Skip to content

Add the anthropic-api credential entry for local tools - #841

Merged
seathatflowsinourveins merged 1 commit into
mainfrom
cc/anthropic-api-credential-20261008
Oct 8, 2026
Merged

seathatflowsinourveins merged 1 commit into
mainfrom
cc/anthropic-api-credential-20261008

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Scope

  • What this PR changes: it adds the anthropic-api entry to adoption/credential-inventory.json, so a Claude API key can be stored through the private credential window (tools/credentials/open_credential_terminal.sh anthropic-api) and injected one command at a time (tools/credentials/credential_run.py anthropic-api -- <command>). It is meant for local tools without an identity provider, such as promptfoo's anthropic: providers and Claude Agent SDK workers. tests/test_credential_run.py adds the id to the reviewed INJECTABLE_IDS set.
  • Base commit: ffd4b8508ae2494bf5bf7ed9df1932537dbb7ecf
  • Lane: lane:foundation
  • Owned paths touched: adoption/credential-inventory.json, tests/test_credential_run.py, and manifests/evidence.json (registry refresh for those two already-registered files).

SOTA sources

  • Anthropic API credits for Max and Team plans: https://platform.claude.com/docs/en/about-claude/api-credits-for-subscribers. A Console organization receives the plan's monthly credits, and a key comes from Console, Settings, API keys.
  • Anthropic Console guidance: use workload identity federation where a CI or cloud provider issues short-lived tokens; local scripts and tools without an identity provider still need an API key.
  • anthropics/claude-code-action v1 action.yml (tag v1.0.245, commit 6fed3ca145920b639991cb756090506e1bcaf515): inputs anthropic_federation_rule_id, anthropic_organization_id, anthropic_service_account_id, anthropic_workspace_id and anthropic_oidc_audience. GitHub Actions use these, so no Anthropic key is stored in GitHub.

Evidence-class table

Claim Evidence class Command / receipt
The key is stored at 0600 in its private env file and is never printed native_proven python3 scripts/credential_status.py → ok anthropic-api … mode=0600 dir=0700 (NativeStack2604, 2026-10-08)
The key works when injected per command native_proven python3 tools/credentials/credential_run.py anthropic-api -- python3 -I <probe> → HTTP 200, model claude-haiku-5-5 (one tiny request, key never printed)
Claude Code sessions keep their Max-plan sign-in source_review the key's variable stays in must_not_be_set in the same inventory file; this PR does not change that list
The inventory and runner contract hold local_integration the unittest run below

Local commands run

$ python3 -m unittest tests.test_credential_tools tests.test_credential_run tests.test_secret_path_guard tests.test_credential_boot_receipt tests.test_host_requests
Ran 286 tests ... OK (skipped=2)
$ python3 scripts/evidence_manifest.py --check
{"files": 10560, "status": "passed"}
$ python3 scripts/validate.py
exit 0 (integrity and scope checks)

Decision record

No separate record. The entry follows docs/decisions/2026-09-29-key-management.md (per-provider 0600 env files injected per command) unchanged. The owner's 2026-10-08 direction is one key for local tools, with federation for GitHub Actions. What would overturn this: a password-manager-backed loader, which is under evaluation, replacing the env-file store for every entry at once.

Host evidence

Not applicable: this PR adds or changes no file under evidence/hosts/.

Checklist

  • New/changed GitHub Actions are pinned to a full commit SHA with a version comment (none changed).
  • New/changed workflows declare least-privilege permissions (none changed).
  • No secrets are printed, logged or committed; no new required GitHub secret was added.
  • No new paid hosting, subscription or billing surface was introduced. The key draws on the owner's existing Max-plan API credits.
  • Peer-owned untracked files and worktrees were preserved.

🤖 Generated with Claude Code

…ntity provider

The owner's direction of 2026-10-08: one Console API key, stored through the private credential window and injected
per command with credential_run.py, for local tools such as promptfoo's anthropic provider and Claude Agent SDK
workers. ANTHROPIC_API_KEY stays in must_not_be_set so no Claude Code session leaves its Max-plan sign-in. GitHub
Actions use Anthropic workload identity federation instead, so no Anthropic key is stored in GitHub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@seathatflowsinourveins seathatflowsinourveins added the lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers label Oct 8, 2026
@seathatflowsinourveins
seathatflowsinourveins merged commit a35369a into main Oct 8, 2026
30 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the cc/anthropic-api-credential-20261008 branch October 8, 2026 06:33
seathatflowsinourveins added a commit that referenced this pull request Oct 8, 2026
### 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant