Add Claude Code OAuth token as fallback credential for sandboxed dev-engine carrier
Discovered from README cross-links · today
Amend spec, wire fallback auth, fix CI test-glob gap, add 7 tests.
What they did
The author's `/dev` carrier — a sandboxed `claude` CLI invocation defined in spec/005 — could only authenticate via `ANTHROPIC_API_KEY`. On a machine with only a Claude Code subscription login and no separate Console API key, it could not run at all; Docker, the egress proxy, and the network path were all confirmed healthy, leaving the credential as the sole gap. They amended spec/005 section 2.6 (arbiter-reviewed over two rounds, then approved) to sanction `CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`) as a fallback, with preference order `ANTHROPIC_API_KEY` > `CLAUDE_CODE_OAUTH_TOKEN`. Because `--bare` does not read `CLAUDE_CODE_OAUTH_TOKEN` per Anthropic's docs, that branch uses `--safe-mode` instead — the arbiter's first review round caught that simply dropping `--bare` would re-enable eight things (hooks, skills, custom commands, subagents, installed plugins, MCP servers, auto memory, CLAUDE.md), not just CLAUDE.md as first assumed. They added `detectCredentialEnvVar()` in `invoker.mjs`, made `run-agent.mjs`'s `defaultExecShell` detect the live credential rather than hardcode one, threaded `credentialEnvVar` through all seven carrier callbacks, excluded both sanctioned names in control #6 of `run-positive-controls.sh`, set `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` unconditionally in docker-compose, and made `dev.mjs` fail fast with `ENVIRONMENT_CHECK_FAILED` when neither credential is set. While in the code they found a pre-existing CI gap: `engine/carrier/*.test.mjs` and `engine/carrier/egress-proxy/*.test.mjs` were never run because the top-level `engine/*.test.mjs` glob doesn't reach subdirectories — including the two test files most central to this PR — and closed it in `.github/workflows/ci.yml`.
How it went
529/529 tests pass (522 prior baseline plus 7 new). The 7 new tests in `invoker.test.mjs` cover both branches' flag/var selection, mutual-exclusion pinning across both, `detectCredentialEnvVar`'s preference/fallback/absence cases, and unknown-value rejection. One real regression was caught and fixed mid-work: `dev.test.mjs`'s 26 `runRepo` happy-path tests didn't inject a fake credential detector and broke against the real credential-less environment once the fail-fast check landed, fixed via the shared `baseRunRepoOverrides` fixture. `prettier --check`, `claude plugin validate --strict` (plugin and marketplace), `sync-route.mjs --check`, `check-roster.mjs`, and `check-version-bump.mjs` all pass; no version bump was needed since `engine/` isn't a distributed-content prefix. Spec returned to `status: finalized`, with a full change record in `revision/2026-11-02-dev-carrier-oauth-token-credential.md`.
Worth knowing
The fallback depends on `claude setup-token` output and on Anthropic's documented behavior that `--bare` ignores `CLAUDE_CODE_OAUTH_TOKEN`; if that changes, the `--safe-mode` branch should be revisited. `--safe-mode` is a broader isolation posture than `--bare`, so behavior differs between the two credential paths and both need ongoing test coverage. The CI glob fix means subdirectory tests now run — expect previously hidden failures to surface on other branches. The arbiter review process (two rounds) is part of this repo's workflow and not reproduced here.
Wiring
Order: spec amendment first (spec/005 section 2.6 plus spec/README.md), then `invoker.mjs` (`detectCredentialEnvVar()`, conditional `--bare`/`--safe-mode`, credential-name forwarding), then `run-agent.mjs`'s `defaultExecShell`, then `carrier-callbacks.mjs` (all 7 callbacks take `credentialEnvVar`), then `run-positive-controls.sh` control #6, then `docker-compose.yml`'s unconditional `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`, then `dev.mjs`'s fail-fast `ENVIRONMENT_CHECK_FAILED`, then the CI workflow glob fix, then tests. Config: credentials come from the environment — `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN` (generated by `claude setup-token`); the carrier detects which is present and forwards only that name into the sandbox. The sandbox runs the `claude` CLI behind an egress proxy in Docker; `--safe-mode` is used on the OAuth path because `--bare` ignores that token. Tests run via `node --test` over `engine/**/*.test.mjs` and `skills/dev/scripts/dev.test.mjs`, with `baseRunRepoOverrides` supplying a fake credential detector.
Try it yourself
In this repo, add `CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`) as a sanctioned fallback credential for the sandboxed dev carrier, preferred order `ANTHROPIC_API_KEY` > `CLAUDE_CODE_OAUTH_TOKEN`. Steps: (1) amend spec/005 section 2.6 to document the fallback and preference order; (2) add `detectCredentialEnvVar()` in `engine/carrier/invoker.mjs` returning the live credential name, and select `--bare` for the API-key branch vs `--safe-mode` for the OAuth branch — do NOT just drop `--bare`, since that re-enables hooks, skills, custom commands, subagents, plugins, MCP servers, auto memory, and CLAUDE.md; (3) make `defaultExecShell` in `engine/carrier/run-agent.mjs` detect the credential instead of hardcoding one; (4) thread `credentialEnvVar` through all carrier callbacks in `engine/carrier-callbacks.mjs`; (5) exclude both sanctioned names in control #6 of `run-positive-controls.sh`; (6) set `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` unconditionally in `docker-compose.yml`; (7) make `skills/dev/scripts/dev.mjs` fail fast with `ENVIRONMENT_CHECK_FAILED` if neither credential is set, and inject a fake credential detector into `dev.test.mjs`'s `runRepo` tests via the shared `baseRunRepoOverrides` fixture; (8) fix `.github/workflows/ci.yml` so `engine/carrier/*.test.mjs` and `engine/carrier/egress-proxy/*.test.mjs` actually run. Add 7 tests in `invoker.test.mjs` covering both branches' flag/var selection, mutual exclusion, preference/fallback/absence, and unknown-value rejection. Run `node --test "plugins/openindu-control-tower/engine/**/*.test.mjs" "plugins/openindu-control-tower/skills/dev/scripts/dev.test.mjs"`, plus prettier, plugin validate, sync-route, roster, and version-bump checks.