This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Google-GitHub Sync is an open-source tool that synchronizes Google Workspace users with GitHub organization members. It automatically invites users with GitHub usernames to a GitHub organization and removes users who are no longer in Google Workspace. Atlassian integration is available behind a feature flag (ATLASSIAN_ENABLED). Slack is used for notifications only (not as a bot). The tool provides scheduled synchronization via cron jobs and an admin dashboard for visual management.
/
├── packages/
│ ├── shared/ # Shared DB schema, API contract, types, utils
│ │ └── src/
│ │ ├── api/ # Effect HttpApi contract (schemas, errors, endpoints) shared by BFF + web
│ │ ├── db/schema.ts # Drizzle ORM schema (audit_log, sync_history, sync_jobs)
│ │ ├── db/index.ts # DB client factory (createDb)
│ │ ├── utils.ts # normalizeGitHubUsername, normalizeEmail
│ │ └── index.ts # Barrel export
│ ├── bff/ # Effect HttpApi server for the dashboard (Process 1)
│ │ └── src/
│ │ ├── index.ts # Server entry (BunHttpServer, CORS, handler layers)
│ │ ├── lib/config.ts # BFF-specific config (Effect Config, read once at startup)
│ │ ├── lib/effect.ts # upstream()/dbQuery()/recordAudit() helpers
│ │ ├── db/index.ts # DB client instance
│ │ ├── handlers/ # HttpApi group handlers (users, teams, repos, sync, activity)
│ │ └── services/ # Google Workspace, GitHub teams/repos/membership (+ Effect service tags)
│ ├── worker/ # Effect.ts sync engine (Process 2)
│ │ └── src/
│ │ ├── index.ts # Worker entry (job processor + cron)
│ │ ├── lib/config.ts # Worker-specific config (Zod validation)
│ │ └── services/ # Effect services (Google, GitHub, Slack, sync engine)
│ │ └── atlassian/ # Feature-flagged Atlassian integration
│ └── web/ # Next.js dashboard (Process 3)
│ └── src/
│ ├── app/ # Next.js App Router pages
│ ├── components/ # React components (layout, users, teams, repos, ui)
│ ├── hooks/ # Custom hooks (use-toast)
│ └── lib/ # API client (generated from shared/api), types, utils
├── docs/ # Architecture documentation
├── package.json # Bun workspace root
├── biome.json # Shared linter/formatter
├── tsconfig.base.json # Shared TS config
└── docker-compose.yml # PostgreSQL
Install dependencies:
bun installStart all services:
bun run devStart individually:
bun run dev:bff # BFF API on port 4000
bun run dev:worker # Sync worker
bun run dev:web # Next.js on port 3001Database:
docker compose up -d # Start PostgreSQL 18
bun run db:push # Push schema to DB
bun run db:studio # Open Drizzle StudioFormat code:
bun run formatLint code:
bun run lintTypecheck:
bun run typecheckRun tests:
bun test --recursiveIMPORTANT: Always run bun test --recursive after making code changes to ensure nothing is broken. Do not skip this step.
The system is split into two backend processes sharing the same PostgreSQL database:
- BFF (
packages/bff): Stateless EffectHttpApiserver serving the dashboard. Implements the contract inpackages/shared/src/api; the web app's client is generated from the same contract. Handles user CRUD, team/repo management (direct GitHub API calls), audit logging, and enqueuing sync jobs. - Worker (
packages/worker): Effect.ts-based sync engine. Picks up jobs fromsync_jobstable by polling every five seconds. Runs cron-scheduled syncs. Sends Slack notifications.
Communication: BFF writes to sync_jobs table, worker picks up and processes jobs. No direct HTTP calls between them.
Cron Jobs:
- GitHub sync (configurable schedule via
CRON_SCHEDULE) - Atlassian inactivity check (feature-flagged,
ATLASSIAN_INACTIVITY_CRON_SCHEDULE)
Schema lives in packages/shared/src/db/schema.ts:
audit_log— Tracks admin actions from BFF (username changes, team/repo ops)sync_history— Permanent log of completed sync executionssync_jobs— Transient job queue (BFF writes, worker consumes)
Next.js 15 with App Router on port 3001:
- User directory with search, filter, sort
- User profile with GitHub username editing
- Teams management (CRUD + members)
- Repos management (settings + team access)
- Async sync: triggers job, polls for status
Tech Stack:
- TanStack Query for data fetching
- shadcn/ui + Tailwind CSS v4 for styling
- Radix UI primitives
Google Workspace Integration:
- Uses service account credentials with domain-wide delegation
- Requires
admin.directory.userscope (read + write) - Impersonates admin user via
subjectfield - Fetches all user pages (300 per page) with custom schema projection; any page failure aborts the snapshot
- GitHub usernames stored in
customSchemas['3rd-party_tools'].GitHub_Username
GitHub Integration:
- Uses Octokit with personal access token
- Requires organization admin permissions
- Uses pagination for listing members and pending invitations
- Invites users by
invitee_id(not by email) - All username comparisons are case-insensitive via
.toLowerCase()
Sync Job Flow:
- BFF inserts row into
sync_jobs(status:pending) - Worker atomically claims the oldest pending job and sets its status to
running - Worker executes sync (Google -> GitHub diff, invite/remove)
- Worker writes result to
sync_history, updatessync_jobsstatus - Dashboard polls
GET /api/sync/jobs/:idfor status
Remove Stop List:
REMOVE_STOP_LISTenv var: comma-separated GitHub usernames never removed from org
Atlassian (feature-flagged):
- Enabled via
ATLASSIAN_ENABLED=true - Suspends users not in Google Workspace
- Checks 3-month / 6-month inactivity (suspend / delete)
- Supports dry-run mode via
ATLASSIAN_DRY_RUN
Each package validates only its own required env vars. The .env file lives at the monorepo root.
Authentication: Browser requests go through the same-origin Next.js API proxy. Dashboard Basic Auth uses DASHBOARD_USERNAME and DASHBOARD_PASSWORD (minimum 16 characters); BFF bearer auth uses API_AUTH_TOKEN (minimum 32 characters). Web reads BFF_URL server-side. No credentials belong in NEXT_PUBLIC_*. Set CORS_ORIGIN to the external dashboard origin for CSRF protection.
BFF env vars:
API_AUTH_TOKEN: Shared server-only token; required, no defaultGOOGLE_APPLICATION_CREDENTIALS: JSON string of service account credentialsGOOGLE_ADMIN_EMAIL: Admin email to impersonateGITHUB_TOKEN: GitHub personal access token with org admin permissionsGITHUB_ORG_NAME: GitHub organization nameDATABASE_URL(default:postgres://postgres:postgres@localhost:5432/dashboard)API_PORT(default:4000)CORS_ORIGIN(default:http://localhost:3001)
Worker env vars:
GOOGLE_APPLICATION_CREDENTIALS,GOOGLE_ADMIN_EMAIL,GITHUB_TOKEN,GITHUB_ORG_NAME: Same as BFFDATABASE_URL(default:postgres://postgres:postgres@localhost:5432/dashboard)CRON_SCHEDULE(default: midnight daily)REMOVE_STOP_LIST: Comma-separated GitHub usernames to never removeSLACK_BOT_TOKEN(optional): Bot user OAuth token for notificationsSLACK_NOTIFICATION_CHANNEL(optional): Channel ID for notificationsATLASSIAN_ENABLED(default:false)ATLASSIAN_API_KEY,ATLASSIAN_ORG_ID,ATLASSIAN_ADMIN_EMAIL: Required when Atlassian enabledATLASSIAN_DRY_RUN: Enable dry-run modeATLASSIAN_INACTIVITY_CRON_SCHEDULE(default: midnight daily)
- Built for Bun runtime (not Node.js), pinned via
packageManagerin rootpackage.json - Uses TypeScript 7 (native compiler) with strict mode enabled
- Effect diagnostics come from
@effect/tsgo, patched intotscby thepreparescript; Effect warnings failbun run typecheck - No build step required for BFF/worker (noEmit: true in tsconfig)
- Next.js uses standard build process; it transpiles
@google-github-sync/shared(web imports onlyshared/api, never the DB barrel) - Worker and BFF use Effect 4 for structured concurrency, typed errors, and service layers
- Prefer imports without extensions, DO NOT use .js extensions in import paths