fix(aos): report unusable credentials on dry run instead of refusing #1002

Merged
coilysiren merged 3 commits from aos/claude/dry-run-auth-diagnostic into main 2026-08-12 07:49:04 +00:00
Member

Stacks on #1001. Merge that first and this rebases clean. The two touch different files (launch.go there, composition.go here).

The problem

A dry run prints a docker plan and starts nothing, so it had no use for the credential it demanded. Without one it aborted.

That is why scripts/check-aos-release.sh carries --auth=false at four call sites, and why #907 and #1000 were the same one-line patch landed twice at different call sites. Each new dry-run caller rediscovers this the hard way.

The change

Auth resolution still runs. A dry run on a host that has credentials still shows the auth path and mount shape that docs/aos-codex-auth.md promises, so that contract is intact.

A failure now writes the same message to stderr and renders the plan anyway. The plan carries no auth mount, because none was staged. A real launch is untouched and still fails closed before Docker starts, which is all #902 asked for.

writeTemporaryClaudeAuth's fail helper cleans up and returns a zero projection, so an errored dry run has nothing left to leak and the existing deferred Close stays a no-op.

The test that pinned the old contract

TestIntegratedStandaloneCodexAuthFailurePrecedesDockerPlan asserted the old behavior using dry-run as its probe, but its actual subject was ordering: auth resolves before the docker plan exists. It splits in two.

  • TestIntegratedStandaloneCodexAuthFailurePrecedesDockerLaunch pins fail-closed on a real launch. Auth fails before prepareStandaloneWorkspace, so docker is never reached, and the test asserts the auth error surfaces and nothing was written.
  • TestIntegratedStandaloneDryRunReportsUnusableAuthAndStillRendersPlan pins the diagnostic, the rendered plan, and the absence of an auth mount it never staged.

That is stronger than what it replaces, which only covered the dry-run path.

Docs

Both docs/aos-codex-auth.md and docs/aos-claude-auth.md record the split, since the old behavior was documented deliberately rather than by accident.

Not changed

The four --auth=false flags in check-aos-release.sh stay. They are no longer required, but they keep the smoke's rendered plan identical on a host that does have credentials, which is worth more than removing four flags.

Verification status

ward exec pre-commit-all green. Both auth tests pass in 6.9s in isolation.

The full aos-cli suite is not confirmed green locally. One run hit Go's default 600s timeout, where earlier runs of the same suite took 259s and 298s on the same machine. My reading is accumulated slowness in the git-backed native-shadow tests under local load rather than a hang introduced here, since the new tests are fast in isolation and their fixture is a local temp repo with no network. That is inference, not a result. CI on this PR is the check that settles it, so please read the suite result here rather than my note.

🤖 Generated with Claude Code

Stacks on #1001. Merge that first and this rebases clean. The two touch different files (`launch.go` there, `composition.go` here). ## The problem A dry run prints a docker plan and starts nothing, so it had no use for the credential it demanded. Without one it aborted. That is why `scripts/check-aos-release.sh` carries `--auth=false` at four call sites, and why #907 and #1000 were the same one-line patch landed twice at different call sites. Each new dry-run caller rediscovers this the hard way. ## The change Auth resolution still runs. A dry run on a host that has credentials still shows the auth path and mount shape that `docs/aos-codex-auth.md` promises, so that contract is intact. A failure now writes the same message to stderr and renders the plan anyway. The plan carries no auth mount, because none was staged. A real launch is untouched and still fails closed before Docker starts, which is all #902 asked for. `writeTemporaryClaudeAuth`'s `fail` helper cleans up and returns a zero projection, so an errored dry run has nothing left to leak and the existing deferred `Close` stays a no-op. ## The test that pinned the old contract `TestIntegratedStandaloneCodexAuthFailurePrecedesDockerPlan` asserted the old behavior using dry-run as its probe, but its actual subject was ordering: auth resolves before the docker plan exists. It splits in two. * `TestIntegratedStandaloneCodexAuthFailurePrecedesDockerLaunch` pins fail-closed on a real launch. Auth fails before `prepareStandaloneWorkspace`, so docker is never reached, and the test asserts the auth error surfaces and nothing was written. * `TestIntegratedStandaloneDryRunReportsUnusableAuthAndStillRendersPlan` pins the diagnostic, the rendered plan, and the absence of an auth mount it never staged. That is stronger than what it replaces, which only covered the dry-run path. ## Docs Both `docs/aos-codex-auth.md` and `docs/aos-claude-auth.md` record the split, since the old behavior was documented deliberately rather than by accident. ## Not changed The four `--auth=false` flags in `check-aos-release.sh` stay. They are no longer required, but they keep the smoke's rendered plan identical on a host that does have credentials, which is worth more than removing four flags. ## Verification status `ward exec pre-commit-all` green. Both auth tests pass in 6.9s in isolation. The full `aos-cli` suite is **not** confirmed green locally. One run hit Go's default 600s timeout, where earlier runs of the same suite took 259s and 298s on the same machine. My reading is accumulated slowness in the git-backed native-shadow tests under local load rather than a hang introduced here, since the new tests are fast in isolation and their fixture is a local temp repo with no network. That is inference, not a result. CI on this PR is the check that settles it, so please read the suite result here rather than my note. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(release): keep the director dry-run smoke unauthenticated
All checks were successful
ci / aos-cli-tests (pull_request) Successful in 27s
ci / ward-doctor (pull_request) Successful in 11s
ci / gate (pull_request) Successful in 55s
b5139e99e8
`check-aos-release.sh` runs two role-default `aoscompose --dry-run` smokes.
The engineer one passes `--auth=false`. The director one, added in #931, did
not. The director launch profile defaults to `claude`, so that call resolves
Claude credentials on a runner that has none.

It stayed green until #974 made Claude auth fail closed. Before that, a
missing `~/.claude/.credentials.json` returned an empty projection and the
launch continued logged out. Since then the same call aborts:

    aoscompose: claude auth: file-backed credentials not found at
    /root/.claude/.credentials.json and host keyring projection is
    unsupported on this platform (use --auth=false for unauthenticated
    commands)

That has failed every `aos-cli-release` run since 2026-08-09 (runs 3130,
3202, 3213), so the AOS CLI release train has published nothing in three
days. This is the same fix #907 applied to the other dry-run smokes.

A dry-run prints a plan and launches nothing, so no smoke in this script
needs real credentials.

Verified with `ward exec pre-commit-all` (all hooks pass, shellcheck
included).

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
fix(aos): forward every Claude credential the auth check accepts
All checks were successful
ci / ward-doctor (pull_request) Successful in 22s
ci / aos-cli-tests (pull_request) Successful in 32s
ci / gate (pull_request) Successful in 53s
0c2a785441
`docs/aos-claude-auth.md` says `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`,
and `CLAUDE_CODE_OAUTH_TOKEN` "cross the container boundary by name when
present, so nothing is projected". Only the first actually crossed.

`claudeEnvironmentAuthPresent` accepted all three and returned an empty
projection, and `forwardedEnvironment` forwarded one. A host holding only
`ANTHROPIC_AUTH_TOKEN` or `CLAUDE_CODE_OAUTH_TOKEN` therefore passed the auth
gate, projected nothing, forwarded nothing, and started the container logged
out. That is #968's symptom reached through a different door.

The Codex side had it right by luck rather than structure: its check-set and
its forward-set were two literals that happened to agree.

Rather than add the two missing keys to the second list, this removes the
second list. Both sets now read `claudeEnvironmentAuthKeys` and
`codexEnvironmentAuthKeys`, so accepting a key and forwarding it are one
edit. `TestEveryAcceptedEnvironmentCredentialCrossesTheBoundary` asserts the
invariant off those slices instead of restating them, so a key added to
either set has to cross or the test fails.

Verified with `ward exec aos-test` and `ward exec pre-commit-all`.

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
fix(aos): report unusable credentials on dry run instead of refusing
All checks were successful
ci / ward-doctor (pull_request) Successful in 13s
ci / aos-cli-tests (pull_request) Successful in 30s
ci / gate (pull_request) Successful in 56s
50d7363dfe
A dry run prints a docker plan and starts nothing, so it had no use for the
credential it demanded. Without one it aborted, which is why the release smoke
carries `--auth=false` at four call sites and why #907 and #1000 were the same
one-line patch landed twice, months apart, at different call sites.

Auth resolution still runs, so a dry run on a host that has credentials still
shows the auth path and mount shape that `docs/aos-codex-auth.md` promises. A
failure now writes the same message to stderr and renders the plan, which
carries no auth mount because none was staged. A real launch is untouched and
still fails closed before Docker starts, which is all #902 asked for.

`TestIntegratedStandaloneCodexAuthFailurePrecedesDockerPlan` pinned the old
contract using dry-run as its probe. It splits in two: fail-closed is now
pinned on a real launch, which asserts the auth error surfaces and no output
is written, and the dry-run diagnostic is pinned separately.

Both auth docs record the split.

Checkpoint: `ward exec pre-commit-all` is green and the two auth tests pass in
isolation. The full `aos-cli` suite is re-running under a longer timeout after
an earlier run hit Go's 600s default, so this is not yet claimed verified.

Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
coilysiren deleted branch aos/claude/dry-run-auth-diagnostic 2026-08-12 07:49:04 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os!1002
No description provided.