fix(auth): dedup container credentials at injection time; add auth mode - #18
Merged
cohix merged 1 commit intoJul 16, 2026
Conversation
vinnie357
force-pushed
the
fix/anthropic-credential-dedup-and-auth-mode
branch
from
July 16, 2026 15:36
d4865ec to
7860912
Compare
Contributor
Author
|
rebased onto main (ef1ed17) and squashed to one commit — conflicts resolved and github shows it mergeable. kept the DynamicWorkflows / maxConcurrentAgents merge additive so both your WI-0096 changes and the auth change are intact (your tests pass alongside the new auth ones). dropped the unrelated render.rs clippy one-liner to keep this scoped to auth — the site moved to render/dialog.rs upstream and clippy on the pinned 1.94.0 toolchain is clean without it. fmt, clippy -D warnings, and make test-fast (2247 tests) all green on 1.94.0; the two real_git tests need a live github remote so they're excluded, same as upstream main here. lmk if you want anything tweaked 🙏 |
vinnie357
marked this pull request as ready for review
July 16, 2026 15:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The container/docker runtime diverged from the sbx (Docker Sandbox) path in a way that caused containers to receive two conflicting Anthropic credentials when users supply
env(ANTHROPIC_API_KEY)via an overlay:CLAUDE_CODE_OAUTH_TOKEN(src/engine/sandbox/dsbx/auth.rs) because sbx has no OAuth-token support yet.env_passthrough(the harness-declaredANTHROPIC_API_KEY) andagent_credentials(the keychainCLAUDE_CODE_OAUTH_TOKEN) as-eflags with no dedup.Our previous fix (PR #17) attempted to detect this at the keychain-resolution layer by reading
std::env::var("ANTHROPIC_API_KEY"). That approach was wrong: it reads the ambient process environment, not the harness-declared env, regressing users who haveANTHROPIC_API_KEYin their process env but not declared as anenv()overlay (they'd lose OAuth auth).Changes
Part A — injection-time credential dedup (all container runtimes)
ResolvedContainerOptions::resolve()now callsdedup_credentials_by_declared_env()after ingesting all options. When a var inenv_passthroughorenv_literalmaps to a provider service viaservice_for_credential, anyagent_credentialsentry mapping to the same service is dropped before the options bag is used by any backend.std::env::var.ResolvedContainerOptionspath).Part B — explicit per-harness
authmodeA new
authfield in.awman/config.json(per-repo) selects the credential injection policy:keychain(default)passthroughenv()overlays.noneResolved via
EffectiveConfig::auth_mode()and gated inAuthEngine::resolve_agent_auth.Shared infrastructure
service_for_credentialis now the canonical table inengine::auth(public). The dsbx module re-exports it rather than maintaining its own copy.CLAUDE_CODE_OAUTH_TOKENis added to the table (→ "anthropic") so the container-side dedup can match it againstANTHROPIC_API_KEYby service. The sbxinject_credentialsis updated to skipCLAUDE_CODE_OAUTH_TOKENvia an unconditional key-name guard (before the service lookup), preserving sbx behaviour.Supersedes
PR #17 (env-sniff approach). This PR replaces it — the new branch is a clean cherry off upstream/main with no Approach-1 guard.
Follow-up fixes (post-review)
Part A gate on resolvable declared env:
dedup_credentials_by_declared_envnow only counts anenv_passthroughentry as "covering" its service when the host value is actually set (lookup_envreturnsSome(non-empty)), mirroringbuild_run_argv's own emission gate. A declared but unset passthrough var no longer suppresses a keychain credential — the passthrough would emit nothing and the container would be left with zero credentials for that service.env_literalentries still always count (they carry an explicit value). Atracing::warn!is emitted whenever a keychain credential is dropped so the situation is never silent.Injectable
lookup_envclosure: The coverage check takes a&dyn Fn(&str) -> Option<String>rather than callingstd::env::vardirectly, so tests stay hermetic without mutating global env state.Doc precision: "Per-harness" → "Per-repo" in
AgentAuthMode,resolve_agent_auth, andEffectiveConfig::auth_mode()doc comments.none/passthroughdocs now correctly say "No KEYCHAIN credential injection; declaredenv()overlays still apply" rather than "No credential injection of any kind."auth_mode()gains a note that single-layer (repo-only) is a deliberate conservative choice.Incidental: the render.rs clippy one-liner is no longer part of this PR — upstream's render module moved to render/dialog.rs and clippy on the pinned 1.94.0 toolchain is clean without it.
Test summary
make test-fast: 2247 passed, 0 failed. the full cargo test run has 2 failures in real_git_run_poll_ci_loop_* tests that need a live github remote (environment-only; upstream main fails a superset in the same clone).
New tests added (all hermetic, injectable closure):
dedup_fn_drops_oauth_when_anthropic_passthrough_set_on_host— passthrough declared AND set → OAuth droppeddedup_fn_retains_oauth_when_anthropic_passthrough_declared_but_unset— passthrough declared but UNSET → OAuth retained (core fix)dedup_fn_retains_oauth_when_no_anthropic_declared— no anthropic var → OAuth retaineddedup_fn_handles_env_literal_source— env_literal always countsdedup_fn_retains_credential_when_no_declared_vars— no-op pathdedup_fn_unmapped_credential_is_never_dropped— custom token retaineddedup_fn_drops_when_credential_name_equals_declared_var— keychain var name == declared var → droppeddedup_fn_multi_service_declared_x_retains_credential_for_y— multi-service: X dropped, Y retaineddeclared_anthropic_env_literal_drops_oauth_token_from_agent_credentials— env_literal through resolve()engine::container::options— Part A dedup viaresolve()anddedup_credentials_by_declared_envunit tests (env_passthrough, env_literal, unmapped credentials, no-op paths)data::config::effective— Part Bauth_mode()precedence tests (default, passthrough, none, explicit keychain)engine::auth— Part Bresolve_agent_authgating tests (passthrough → empty, none → empty, keychain → Ok)