GitHub Codespaces
The devcontainer is pre-configured for GitHub Codespaces — no local setup required.
Prerequisites
Set VS Code Desktop as your default Codespaces editor:
- Go to github.com/settings/codespaces.
- Under Editor preference, select Visual Studio Code.
- Save.
This ensures Codespaces open in VS Code Desktop (with full extension support) rather than the browser editor.
Creating a Codespace
- On the repository page, click Code → Codespaces → Create codespace on main (or your branch).
- Select the 4-core / 16 GB machine type.
- Wait for the container to finish building.
postCreate.shruns automatically: installs dependencies, bootstraps all dbt projects (dbt deps+dbt parsein parallel), and injects secrets from 1Password. First creation takes a few minutes. VS Code may show a Python interpreter warning during this phase — the base container image seeds a machine-scoped setting pointing to/usr/local/bin/pythonbefore the project venv exists.postCreate.shoverwrites that setting early in its run, so the warning is transient and resolves on its own without any action needed.
After the Codespace opens
Allow Automatic Tasks
The first time the workspace opens, VS Code will ask:
"This folder has tasks that run automatically when you open this folder. Do you allow automatic tasks to run when you open this folder?"
Click "Allow and Run". This is required for the automated setup to work.
If you dismiss or deny this prompt, the setup task will not run. To fix it,
add "task.allowAutomaticTasks": "on" to your local VS Code User
Settings (Ctrl+Shift+P → Open User Settings (JSON)), then reopen
the Codespace.
The Setup: Post-Build Init VS Code task runs automatically when the workspace opens. It walks you through each step in order:
- GCloud Application Default Credentials — opens a browser for Google OAuth. Required for BigQuery, dbt, and all GCP-authenticated tooling.
- Claude Code authentication — opens a browser for Claude login.
- Claude Code plugin installation — installs plugins listed in
.claude/settings.json. Re-runs automatically when the plugin list changes. - dbt dev dataset reminder — prints a note about running dbt: Build Init for first-time setup.
On subsequent opens (restarts, reconnects), the task re-checks each step and skips anything already configured.
Dismiss VS Code extension prompts — all required extensions are already
configured in devcontainer.json; dismiss any extension install prompts VS Code
shows.
Wait for dbt Power User to finish parsing — the extension parses all
projects in the background, which pegs CPU and makes the extension unresponsive
until complete. Use htop to monitor; wait for CPU to settle before using the
extension. This is also the most common cause of "extension not responding"
errors.
Reload the window once background processes finish (Ctrl+Shift+P → Developer: Reload Window) for a clean editor state.
First-time dbt setup
If this is your first Codespace, you need to build your personal dev datasets in BigQuery. Run the dbt: Build Init task:
- Ctrl+Shift+P → Tasks: Run Task → dbt: Build Init
- Select a project (or all for the full build).
Regional projects (Camden, Miami, Newark, Paterson) build in parallel; kipptaf builds last since it depends on them.
Manual tasks
Individual setup steps are also available via Ctrl+Shift+P → Tasks: Run Task:
| Task | When to use |
|---|---|
| GCloud: Application Default Login | Re-authenticate after token expiry |
| Claude: Login | Re-authenticate Claude Code |
| Claude: Install Plugins | Manually re-install project plugins |
| dbt: Build Init | Rebuild dev datasets (pick projects) |
Subsequent sessions
postStart.sh runs automatically on resume (updates uv, syncs dependencies).
The setup task re-checks auth and plugin state — you only need to
re-authenticate if credentials have expired.
Container Internals
Setup Lifecycle
postCreate.shruns once on container creation — installs dependencies, sets up GCP auth, removessudopostStart.shruns on every container start — refreshes GCP ADC, Claude auth, VS Code tasks. Keep it idempotent.- The "Setup: Post-Build Init" task polls with
pgrepuntilpostCreate.sh/postStart.shfinish — VS Code firesfolderOpenbefore they complete, so the guard is intentional; do not remove it.
Secret Injection
Secrets are fetched on demand from 1Password by tests/conftest.py when pytest
runs. The 1Password service account token is saved to
/etc/secret-volume/.op-token during postCreate and revoked from interactive
shells in postStart.
- Adding a new secret: update
_FILE_SECRETSintests/conftest.pyand the.env.tpltemplate in.devcontainer/tpl/.
1Password CLI Commands
op inject— replacesop://references in template files; used for.env.tpland other templates with embedded secret URIsop read— reads a single secret or file attachment byop://URI; supports--out-filefor binary output; use for multi-attachment items (e.g.,op read "op://vault/item/filename" --out-file path)op document get— downloads an item's document attachment by item name/UUID (notop://URIs); no--file-nameflag exists, so it cannot select among multiple attachments — useop readinstead
GCloud Authentication
ADC in Codespaces works via user-impersonation of the service account
codespaces@teamster-332318.iam.gserviceaccount.com. Access is split into two
independent layers:
- Developer access: all members of
teamster-analysts@apps.teamschools.orgare grantedroles/iam.serviceAccountTokenCreatoron the SA, which allows them to impersonate it via--impersonate-service-account - SA permissions:
codespaces@has its own direct IAM bindings on the project — it is not a member ofteamster-analysts@and does not inherit group roles. This prevents a self-impersonation loop and keeps the SA's permissions explicitly auditable in IAM.
To grant a new developer access: add them to
teamster-analysts@apps.teamschools.org.
GCloud quirks
- To check if ADC is valid, use
gcloud auth application-default print-access-token;gcloud auth listonly checks user accounts, not ADC — they're independent bqCLI requiresgcloud auth login(user credentials), not just ADC- Running
gcloud auth loginbeforegcloud auth application-default loginin the same script causes "This app is blocked" errors — keep them separate bq lspaginates at ~50 rows by default — use--max_results=Nfor large result setsgcloud auth application-default logindoesn't work in Codespaces — the localhost OAuth callback can't reach the container.gcloud-application-default-login.shuses--impersonate-service-account=codespaces@teamster-332318.iam.gserviceaccount.comto work around this; the user must haveroles/iam.serviceAccountTokenCreatoron that SA- The default gcloud OAuth client blocks
drive.*scopes — never add them togcloud auth application-default loginwithout--impersonate-service-account gcloud auth application-default set-quota-projectfails with impersonated credentials — use--billing-projectflag on the login command instead
Claude Code Setup
CLAUDE_CODE_OAUTH_TOKEN(notANTHROPIC_AUTH_TOKEN) is the correct env var for OAuth token auth — use as a personal Codespace secret to bypass credential store entirely- GitHub Actions workflows use
claude_code_oauth_tokeninput (notanthropic_api_key) - The CLI detects
opon$PATHand tries to use it as a credential backend —OP_SERVICE_ACCOUNT_TOKENis set to a dummy value after secret injection (postStart.sh) to makeopfail fast instead of prompting claude-plugins-officialmust be listed in.claude/settings.jsonextraKnownMarketplaces(pointing toanthropics/claude-plugins-official) soclaude-install-plugins.shexplicitly refreshes it viamarketplace add --scope projectbefore installs run — omitting it causes "Plugin not found in marketplace" errors for all*@claude-plugins-officialplugins- Check Claude auth with
"${CLAUDE}" auth status | grep -q '"loggedIn": true'; flag-file guards are redundant and produce false "not logged in" errors — check auth directly - On a fresh rebuild the Claude Code extension may not be installed when
folderOpenfires;$CLAUDEwill be empty — poll for the binary rather than silently skipping
Container Quirks
apt-getandDAC_OVERRIDE: devcontainer features runapt-get updateduringdocker build, leaving stale files in/var/lib/apt/lists/partial/owned by_apt:rootwith mode0700.--cap-drop=DAC_OVERRIDEwould prevent root from accessing these directories at runtime — do NOT add it back.DAC_OVERRIDEis safe to keep becausesudois removed at the end ofpostCreate.sh, making the capability irrelevant after setup.sudoremoved: at the end ofpostCreate.sh— privileged setup (gcloud components, Helm) must go inpostCreate.sh, not later. To add new components, updatepostCreate.shand rebuild the container./etc/secret-volumetmpfs permissions: mounted0777(world-writable) sopostCreate.shcan write the token file without sudo. File-based secrets are written600(owner-read-only) bytests/conftest.pyon demand.uid/gidmount options were not used — they are not supported on all Codespaces hosts.--cap-addstripped: Codespaces silently strips--cap-addfromrunArgs— namespace-based sandboxing (bwrap, unshare) will not work.- dbt Power User extension: activates on
workspaceContains:**/dbt_project.ymland auto-runsdbt deps(controlled bydbt.installDepsOnProjectInitialization, defaulttrue) anddbt parse(not configurable) on startup. Risk: extension may activate beforeuv syncinstalls dbt-core. Ifdbt depsruns inpostCreate.sh, setdbt.installDepsOnProjectInitializationtofalseto avoid duplicate work. - dbt Power User project scanning:
dbt.allowListFoldersrestricts which paths the extension scans fordbt_project.yml— usesstartsWithmatching. The extension also has a built-innotInDBtPackagesfilter. Manifest is read fromtarget/via Python bridge, not VS Code file watcher, sofiles.watcherExcludeontarget/doesn't break it.