Repository navigation
Register optional Anthropic Admin API credential metadata - #850
seathatflowsinourveins wants to merge 2 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b69f8c762
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "kind": "private_env_file", | ||
| "path_template": "${XDG_CONFIG_HOME:-$HOME/.config}/native-agent-stack/anthropic-admin.env" | ||
| }, | ||
| "variables": ["ANTHROPIC_ADMIN_KEY"], |
There was a problem hiding this comment.
Add the Admin variable to the secret guard
Once an owner stores this full-access Admin key and runs a command through credential_run.py, the Claude command guard does not recognize ANTHROPIC_ADMIN_KEY: guard.check("echo $ANTHROPIC_ADMIN_KEY") and Python environment lookups both return no refusal, allowing commands to access or transform the key beyond the output masker's coverage. This also makes tests.test_secret_path_guard.SecretPathGuardTests.test_inventory_secret_names_are_all_guarded fail because the new inventory variable is absent from scripts/hooks/secret_path_guard.py::SECRET_NAMES; update the guard and its associated registrations alongside the inventory entry.
Useful? React with 👍 / 👎.
### Scope - What this PR changes: it adds the optional `anthropic-api-2` entry directly after `anthropic-api` in `adoption/credential-inventory.json`, so one command can select an additional Console key. It is an additional key: nothing is replaced. The setter, the runner and the guard are unchanged: the setter and the runner read the inventory, and the guard's rules already cover the variable and the whole store directory. Both entries declare only `ANTHROPIC_API_KEY`, which stays in `must_not_be_set`. - Base commit: `e48de2e6fa1dd3b740af4793ea5824ea35476dba` - Lane: `lane:foundation` - Owned paths touched: `adoption/credential-inventory.json`, `docs/secret-storage.md`, `docs/decisions/2026-10-08-anthropic-api-second-key.md`, `docs/harness-defaults.md`, `tests/test_credential_run.py`, `catalogs/foundation/upstream-surface-dispositions.json`, and `manifests/evidence.json` (the hashes of those six files, written by `scripts/host_receipts.py:register_file`). The new entry differs from the first in exactly six fields: `id`, `label`, `store.path_template` (file `anthropic-api-2.env` in the same directory), `loaders`, `rotation` and `notes`. Class, status, lane, variables, optional and pointer variables, consumers and store kind are identical. The repository precedent is `alpaca-paper-2` (#481, `c26800f3`). **Scope beyond the brief.** Four edits go beyond adding the entry. They are declared here so that the read of this PR covers them: 1. **One sentence of the first entry's `notes` is corrected.** Old: "exported in a shell it would override every Claude Code session's native sign-in". New: "a shell export can switch Claude Code from subscription sign-in to API billing (headless mode uses the key when present; interactive mode first asks for approval)". Source: Claude Code's [environment variables](https://code.claude.com/docs/en/env-vars#variables) page, where a present key is always used in non-interactive mode (`-p`) and is approved once in interactive mode before it overrides the subscription. The new entry gives the same reason, so the two entries agree. No other field of `anthropic-api` changes. 2. **One row is added to the anti-pattern log of `docs/harness-defaults.md`.** It records that correction, which that page's "Record a correction the same turn" rule asks for. 3. **`docs/secret-storage.md` gains the first key's table row as well.** The entry table had no `anthropic-api` row. It now lists both entries, followed by one paragraph on selecting a key for one command. 4. **Five line citations into `docs/secret-storage.md` are updated in `catalogs/foundation/upstream-surface-dispositions.json`,** because the added rows moved the cited lines down by 18. The `source` locators of `CLAUDE_CODE_ENABLE_TELEMETRY` (1697 → 1715), `OTEL_LOG_USER_PROMPTS` and `OTEL_LOG_ASSISTANT_RESPONSES` (1684 → 1702), and `OTEL_LOG_TOOL_CONTENT` and `OTEL_LOG_RAW_API_BODIES` (1686 → 1704) change; no disposition or reason does. Hosted `validate-shard-3` failed five subtests of `DispositionCitationTests.test_every_cited_line_names_the_key` at `88d981c7`; #858 made the same repair for its own insertion. ### SOTA sources Public primary documentation, fetched 2026-10-08; every cited anchor resolves. - Anthropic [API overview, Getting API keys](https://platform.claude.com/docs/en/api/overview#getting-api-keys) and [Authentication, Create and use a key](https://platform.claude.com/docs/en/manage-claude/authentication#create-and-use-a-key): keys are created on the Console key page, the client SDKs pick up `ANTHROPIC_API_KEY` automatically, and a key scoped to one workspace needs no workspace header. - Anthropic [Get your API key, Choose a key type](https://platform.claude.com/docs/en/get-api-key#choose-a-key-type), [Workspaces, API keys and resource scoping](https://platform.claude.com/docs/en/manage-claude/workspaces#api-keys-and-resource-scoping) and [Set workspace limits](https://platform.claude.com/docs/en/manage-claude/workspaces#set-workspace-limits): personal, service-account and legacy workspace keys; a multi-workspace key needs the `anthropic-workspace-id` header; spend limits are set per workspace. - Claude Code [environment variables](https://code.claude.com/docs/en/env-vars#variables) and [authentication precedence](https://code.claude.com/docs/en/authentication#authentication-precedence): in non-interactive mode (`-p`) a present key is always used; in interactive mode the key is approved once before it overrides the subscription. These support the corrected wording. - Anthropic [WIF reference, Profile configuration file](https://platform.claude.com/docs/en/manage-claude/wif-reference#profile-configuration-file): the configuration-profile alternative that the decision record names. - [This repository at `c9ab2212142398234b6ba39a4cf33c5c2a6a643e`](https://github.com/seathatflowsinourveins/native-agent-stack/tree/c9ab2212142398234b6ba39a4cf33c5c2a6a643e), the reference implementation this change follows; the cited files are unchanged at the base commit: inventory entries `anthropic-api` (#841, `a35369aa`) and `alpaca-paper-2`; `tools/credentials/set_credential.py:load_entry`; `tools/credentials/credential_run.py:find_entry` and `injectable`; the guard's `SECRET_NAMES`, `STORE_PATHS` and `ENV_FILE_WORD` in `scripts/hooks/secret_path_guard.py`; the `RunnerCase` fixtures in `tests/test_credential_run.py`; `scripts/host_receipts.py:register_file` and `scripts/validate.py`. No credential runtime or dependency is added. ### Evidence-class table | Claim | Evidence class | Command / receipt | | --- | --- | --- | | The Console key and environment contract, and the mode-dependent sign-in wording | source_review | The official pages linked above, fetched 2026-10-08 | | The new entry differs from the first in six fields only; the inventory goes from 20 to 21 entries and from 10 to 11 injectable ids; of `anthropic-api`, only `notes` changes | source_review | Field-by-field comparison of both entries against `origin/main`, using the runner's own `injectable()` | | The setter and the runner select separate files, leave the first file unchanged when the second is written, accept the same bare and quoted grammar, inject only the selected value and refuse expansion | local_integration; synthetic | `InjectionTests.test_second_anthropic_key_uses_the_same_setter_and_runner_grammar`: temporary store, generated fake values | | The guard needs no edit: it lists no ids, its store rule covers the whole store directory, and the variable is already a guarded name | source_review; synthetic | `STORE_PATHS` and `SECRET_NAMES` in the guard; `tests.test_secret_path_guard`; `guard.read_command` on nine synthetic command forms returns the same reason for both ids (four store-path forms `credential_store_path`, the documented runner form allowed) | | No leak in the six files of the first commit | local_integration | `betterleaks dir` 1.9.0 with `.gitleaks.toml` on a copy of those six files: no leaks, rc 0. The required gate is CI's pinned gitleaks 8.30.1, which is not installed on this host; hosted `secret-scan` passed at `88d981c7` | | Each of the five re-pointed catalog locators names its key again, and no other maintained citation moved | local_integration; source_review | `tests.test_upstream_surface_watch`: 5 of 128 repository citations failed with the catalog at `88d981c7`, 0 fail now. Of 263 line citations into the four edited files, 126 point past the inserted lines: these 5; 46 pinned to a commit; 75 in dated records, frozen evidence or code comments that already pointed elsewhere before this PR or carry their own source revision | Not measured here: the actual key type, the workspace spend limit, credit, fast mode and provider acceptance. A multi-workspace key needs the `anthropic-workspace-id` header and is outside this single-variable entry. ### Every changed existing test expectation One constant changes, and two existing tests assert on it. The exact injectable set was `alpaca-paper`, `alpaca-paper-2`, `sec-contact`, `databento`, `typesafe`, `omniroute`, `tavily`, `claude-oauth-token`, `canary-e2e` and `anthropic-api`. `INJECTABLE_IDS` now adds only `anthropic-api-2`. | Existing test | Old contract → new contract | | --- | --- | | `InjectionTests.test_only_the_selected_entrys_own_pointer_variables_stay_in_the_child` | Exactly the ten ids above are injectable, and each keeps only its own pointer variables → the same contract for eleven ids, adding `anthropic-api-2`, whose own pointer list is empty. | | `InventoryAndGrammarTests.test_every_injected_name_is_masked_or_public` | Exactly those ten ids classify every injected variable as masked or public → eleven ids, adding the masked required variable of `anthropic-api-2`. The exact optional-variable classification is unchanged, because the new entry has none. | No other existing assertion is edited. `NOT_INJECTABLE_IDS` is unchanged. The status module checks the canonical inventory dynamically and by subset. The boot-receipt allowlist test compares the receipt's rows with the canonical inventory, so its expected id sequence follows the inventory (20 → 21 rows; file-kind rows 18 → 19). The guard's tie tests read the inventory's variable and pointer names, which do not change. Fixtures are unchanged. `InjectionTests.test_second_anthropic_key_uses_the_same_setter_and_runner_grammar` is new coverage, not a relaxed assertion. ### Local commands run One module per command (no suite discovery). The twelve modules below ran on the tree committed as `5117734b`, the first commit on `e48de2e6`. The second commit, `88d981c7`, only removes two process claims from the decision record that nothing in the repository can verify; the docs module and the validator were run again on its tree. The third commit, `75fa64b3`, re-points five catalog citations; the last block covers it and the whole suite. ``` $ timeout 600 nice -n 10 ionice -c2 -n7 python3 -m unittest tests.<module> -v tests.test_credential_status rc 0 Ran 48 OK tests.test_credential_tools rc 0 Ran 34 OK tests.test_credential_run rc 0 Ran 94 OK tests.test_credential_boot_receipt rc 0 Ran 21 OK tests.test_secret_path_guard rc 0 Ran 90 OK (skipped=2: scratch adapter supplied by the permanent-test mutation driver) tests.test_canary_proof rc 0 Ran 143 OK (skipped=134: reviewed ripgrep unavailable on this host) tests.test_codex_worker_lane rc 0 Ran 123 OK (skipped=12: real app-server runs need NAS_CODEX_INTEGRATION=1) tests.test_host_requests rc 0 Ran 49 OK tests.test_new_wsl_definitive_defaults rc 0 Ran 130 OK (skipped=3: GNU chmod --reference and readlink -f commands) tests.test_adoption_docs_consistency rc 0 Ran 39 OK (skipped=1: no profile's coverage differs between the pinned release and HEAD) tests.test_sota_convergence rc 0 Ran 157 OK tests.test_ecosystem_manifest rc 0 Ran 82 OK $ python3 scripts/evidence_manifest.py --check rc 0 {"files": 10901, "status": "passed"} $ timeout 900 nice -n 10 ionice -c2 -n7 python3 scripts/validate.py rc 0 {"components": 70, "hashed_files": 10901, "profiles": 4, "receipts": 239, "status": "passed"} ``` On the tree committed as `88d981c7`: ``` $ timeout 600 nice -n 10 ionice -c2 -n7 python3 -m unittest tests.test_adoption_docs_consistency -v rc 0 Ran 39 OK (skipped=1) $ timeout 900 nice -n 10 ionice -c2 -n7 python3 scripts/validate.py rc 0 {"components": 70, "hashed_files": 10901, "profiles": 4, "receipts": 239, "status": "passed"} ``` Hosted validate at `88d981c7` (run 37846469750) failed one shard: `validate-shard-3`, `Ran 1514 tests`, `FAILED (failures=5, skipped=127)`. All five failures were the stale catalog citations. The third commit, `75fa64b3`, fixes them. On its tree: ``` $ timeout 600 python3 -m unittest tests.test_upstream_surface_watch -v rc 0 Ran 114 OK (skipped=6) $ timeout 600 python3 -m unittest tests.test_adoption_docs_consistency tests.test_credential_run rc 0 Ran 133 OK (skipped=1) $ timeout 900 python3 scripts/validate.py rc 0 {"components": 70, "hashed_files": 10901, "profiles": 4, "receipts": 239, "status": "passed"} $ for m in tests/test_*.py: timeout 600 python3 -m unittest tests.$m (each module on its own, no discovery) 276 modules: 255 rc 0, 21 rc 1; 10,725 tests ran, 911 skipped ``` The 21 nonzero modules all passed in hosted validate at `88d981c7`. Locally they fail for host reasons, not because of this diff: - **18 modules: `exchange_calendars` is not installed** in this host's Python 3.13. Hosted installs 4.13.2 from `.github/requirements-calendar.txt`. Sixteen modules do not import: the 13 `test_adaptive_paper_*` modules other than credential_race, plus `test_alpaca_admission_regressions`, `test_native_faults_min` and `test_order_throughput`. `test_ingest_snapshot` has 2 errors. `test_adaptive_paper_credential_race` has 5 mutation self-test failures, because its pristine run imports the runner module. All 18 fail the same way on a checkout of the base `e48de2e6`. - **`test_token_e2e_grader`:** `git clone --local` from the worktree (on disk) into `/tmp` (tmpfs) fails with `Invalid cross-device link`. It passes on the base checkout, which lives on tmpfs. - **`test_windows_terminal_defaults`:** the Claude Code client installed on this host reports a `plugin_notification` notification type that has no repository decision. The base gives the same failure. - **`test_new_wsl_mcp_conformance_lifecycle`:** host-state dependent. Three runs at this head gave a listener assertion, a missing `cleanup.json`, then a pass; it passes on the base. The fourth commit, `67d9e8d5`, corrected a stale comment citation in `blueprints/runtime-workers/openhands/resolver/patch_policy.py`. That citation was already wrong on main, so it is outside this PR's scope and will be handled separately. The fifth commit, `2fa607e1`, reverts it. The head's tree is identical to `75fa64b3`'s (tree `d399373a`). On it, `timeout 900 python3 scripts/validate.py` returns rc 0. ### Decision record `docs/decisions/2026-10-08-anthropic-api-second-key.md`: the decision, the handling rules, the sources, the `alpaca-paper-2` precedent, the correction, the evidence boundaries, the alternatives and what would reopen the decision, and the inverse. ### Host evidence Not applicable: this PR adds or changes no file under `evidence/hosts/`. ### Checklist - [x] New/changed GitHub Actions are pinned to a full commit SHA with a version comment (none changed). - [x] New/changed workflows declare least-privilege permissions (none changed). - [x] No secrets are printed, logged or committed; no new required secret was added. No credential value or credential store was read, inspected, created or changed for this PR: the tests use a temporary store and generated fake values, and the setter, launcher and runner were not run against a real entry. - [x] No new paid hosting, subscription or billing surface is introduced by this declaration. The key itself is supplied later through the existing hidden prompt; its credit and spend limit are not measured here. - [x] Peer-owned untracked files and worktrees were preserved. ### Landing notes - This PR declares a store; it neither creates one nor opens the paste window. - #850 (open) inserts `anthropic-admin` at the same position in the inventory and edits the same `INJECTABLE_IDS` line; #769 and #770 (drafts) also touch the inventory, `docs/secret-storage.md` and `tests/test_credential_run.py`. Whichever lands second needs a rebase and a fresh `register_file` pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Scope
Register the optional
anthropic-admininventory id so the owner can later store a Claude Console Admin API key with the existing hidden-input setter. The row declares only the foundation lane, existing provider-key class, private file path template andANTHROPIC_ADMIN_KEY; the runbook cites the official key type and variable name. The existing credential tests classify the new id, the secret-name guard protects its variable, and current file hashes/pins are registered. The Admin runbook section is appended after existing text so established source-line citations remain valid.28af9b9321d0492f6cdabe75091376560e7cdc4d.lane:foundation, matching the PR label.adoption/credential-inventory.json,docs/secret-storage.md,tests/test_credential_run.py,scripts/hooks/secret_path_guard.py,adoption/hooks/claude/SHA256SUMS, and derived registrations inmanifests/evidence.json.SOTA sources
x-api-key: $ANTHROPIC_ADMIN_KEY, establishing the variable name without embedding a value.Evidence-class table
source_reviewlocal_integrationcredential_status.inventory_errors(...)returned no errors;set_credential.load_entry("anthropic-admin", env={})accepted the metadata without invoking the writerlocal_integrationnative_provenpython3 scripts/validate.pyrc0; 69 components, 10,820 hashed files, 4 profiles, 226 receiptssource_reviewLocal commands run
Scoped regression checks
The existing guard now covers
ANTHROPIC_ADMIN_KEYon the same tuple line asANTHROPIC_API_KEY, preserving guard source citations. Its current SHA256SUMS entry matches the source; historical guard receipts retain their historical pins. The runbook's original 2,525 lines remain an unchanged prefix, so existing disposition citations still point to their keys.Final command:
CI=true python3 -m unittest -q tests.test_secret_path_guard tests.test_upstream_surface_watch tests.test_credential_status tests.test_credential_tools tests.test_credential_run tests.test_credential_boot_receipt— rc0; 395 tests in 153.711s, 9 skips. The guard/citation/credential suite passes in its native Actions mode. A normal local run reported onlytest_host_profile_copy_is_verbatim: the installed host guard differs from the revised repository source. The current host guard is not deployed/updated by this PR work, and host parity remains unverified; the native CI mode skips that deployment assertion. Existing credential fixtures do not read a real store.Decision record
No new architectural decision: this follows the existing secret-storage decision and CC 080508Z row 9. The owner provisions/stores the key later through
tools/credentials/set_credential.py anthropic-admin. Admin API connectivity, valid credentials and SDK automatic environment discovery are UNVERIFIED.Host evidence
Not applicable; this PR adds no host receipt or platform-status claim.
Checklist
python3 scripts/validate.pyand the credential tests pass.This foundation metadata change is off the paper path and complies with the CC freeze/quiet window: it performs no runtime installation, runtime-worker wave, gateway restart or Alpaca key use.