Uh oh!
There was an error while loading. Please reload this page.
Attach declares the gateway first-party so Claude keeps its real context window - #438
Conversation
…ext window Claude Code only grants a native-1M model its 1M context window when ANTHROPIC_BASE_URL points at api.anthropic.com. Behind the HypAware gateway it assumes 200k, so an attached session reports ~18% context where the same session direct reports ~4%, and context warnings and auto-compact fire against the wrong denominator. The token usage itself is unchanged; only the assumed window is. Attach now also writes env._CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL="1", managed by the same ownership rule as ENABLE_TOOL_SEARCH: only ever added when absent or already recorded as ours, recorded in the `_hypaware` marker so the core undo removes exactly what attach added, and never clobbering a value the user set themselves. The declaration is accurate - the gateway is a byte-transparent pass-through to api.anthropic.com. The two managed env additions are now one table (MANAGED_ENV_ADDITIONS) plus a single ownership helper, so a third key cannot drift from the undo record. LLP 0045 Part 3 gains the matching section, including the caveat that the key is underscore-prefixed and undocumented (last verified against Claude Code 2.1.220) and what to re-verify if the symptom returns. Co-Authored-By: Claude <noreply@anthropic.com>
…rop an em dash The _CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL declaration asserts a property of whatever the gateway forwards to, not of the gateway itself (unlike ENABLE_TOOL_SEARCH). The anthropic upstream's base_url is ordinary config, so repointing it makes the declaration false while attach still writes it unconditionally. Record that as a stated precondition in LLP 0045 Part 3 and at MANAGED_ENV_ADDITIONS rather than leaving it implicit. Also drop the em dash this PR reintroduced into the undo-record comment (CLAUDE.md forbids U+2014 anywhere). Co-Authored-By: Claude <noreply@anthropic.com>
philcunliffe
commented
Jul 29, 2026
Verdict: approve after the two fixes pushed below ( |
The PR asserted the flag gates "traceparent propagation, oauth beta headers". Read off the shipped Claude Code bundle (2.1.215), the oauth part is wrong and the list is materially incomplete. `_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL` is one branch of a single is-first-party predicate that also gates the context-1m beta header, an extended usage-limit header, Anthropic error reporting, the org policy-limits fetch and memory-sync eligibility. The oauth bearer token and its `oauth-2025-04-20` beta header ride an active oauth session, not this predicate, so the flag changes no credential's destination. That distinction is what bounds the documented precondition: a repointed `upstreams[].base_url` leaks no secret it was not already going to receive, and the real failure is the assumed window being too large for a 200k upstream, which fails loudly. Record both in LLP 0045 and at MANAGED_ENV_ADDITIONS so the next reader re-verifies against the right list. Co-Authored-By: Claude <noreply@anthropic.com>
philcunliffe
commented
Jul 29, 2026
Neutral review, round 2 of 2 (final)Head reviewed: Round 1 record: #438 (comment) Round 1's own two fixes: both verified correct and complete
The substantive open question, judged independentlyRound 1 left the unconditional first-party declaration as a documented precondition rather than a code check. I reach the same disposition, but on evidence rather than deference, and the PR's justification for it was partly wrong. I read the predicate off the shipped Claude Code bundle installed in this environment ( functionNd(){if(Z._CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL)return!0;returnGUn()}functionGUn(){lete=process.env.ANTHROPIC_BASE_URL;if(!e)return!0;returntPe(e)}functiontPe(e){try{lett=newURL(e).host;return["api.anthropic.com"].includes(t)}catch{return!1}}The env var is one branch of a single is-first-party predicate. What it gates:
The PR's stated justification is wrong on one item. The code comment, LLP 0045 and the PR body all said the flag gates "traceparent propagation, oauth beta headers". It does not gate oauth beta headers. The beta list builder pushes if(qo()||oGn()&&!u1r()&&CH())t.push($et);and the auth-header resolver Why that makes the precondition proportionate rather than a security problem. Because credential choice is not gated, the flag sends no secret to any host it was not already going to reach. Attach has already pointed The real residual harm is the window itself, and it runs in the unsafe direction: a repointed upstream that genuinely is 200k now gets warned about and auto-compacted far too late, and an over-long request fails at the upstream. That failure is loud (an upstream API error, not silent corruption), confined to a configuration the product does not otherwise support, and the only code fix is to plumb gateway upstream config into a settings writer that today receives a port. Disproportionate. Precondition is the right call. FindingsLOW - the enumeration of what the flag gates was inaccurate and materially incomplete. FIXED in Positive verification: Reviewed and found clean
Non-blocking observations, deliberately not fixed
Unresolved for a humanNothing blocking. One item to be aware of at merge time: the flag was verified here against Claude Code 2.1.215 (the version installed in this environment); the code comment claims "last verified 2.1.220". I could not check 2.1.220 directly. The predicate is present and behaves as documented in 2.1.215, so the claim is corroborated but not confirmed at the exact stated version. Review cap reached (2 of 2 rounds). This PR is a draft; per policy I did not mark it ready or merge it. |
philcunliffe
commented
Jul 29, 2026
Neutral triage: PR can merge safelyHead reviewed: Review hit its 2-round cap (round 1: #438 (comment), round 2: #438 (comment)) with four residual findings recorded as "not fixed in this PR". I re-derived each against the code at head rather than taking the recorded severity at face value. Classification (all non-blocking)
All four are preference-level or documentation-only: none constitute wrong behavior, data loss caused by this PR, a security hole, a crash, or a perf regression relative to what already ships on Filed a follow-up issue enumerating all four (with file:line and the "why deferred" reasoning) so they don't get lost: #440 Per LLP 0017, this PR is judged safe to ship. Left as draft, not marked ready, not merged - that's a later tick's job. |
Uh oh!
There was an error while loading. Please reload this page.
…ified-version stamp (#449) * claude attach: a non-string user env value is the user's, and one verified-version stamp (#448) Finding 1: `manageEnvAdditions`'s ownership gate tested the *type* of the existing value (`typeof env[key] === 'string'`), so a hand-written JSON boolean or number at a managed key fell straight through it. Attach coerced the value, recorded the key in the `_hypaware` marker's `managed.env`, and the core undo - doing exactly what that record authorized - then deleted a setting the user owned. Reproduced end to end: `ENABLE_TOOL_SEARCH: true` plus `_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL: 0` came back from attach as `"true"`/`"1"`, and detach removed both, pruning the emptied `env` block with them. The `0` case is the sharpest: that is the user turning the flag off, and attach reversed their intent before deleting it. The guard now tests presence (`key in env`), which is what the helper's JSDoc always promised and what LLP 0045's "only manage the key when it is ours" rule means. Coercion was never intended, so the guard moves to match the doc rather than the doc moving to bless the data loss. Finding 2: the three verified-version stamps disagreed - settings.js and llp/0045:373 said 2.1.220, llp/0045:330 said 2.1.215. Both values entered in the same commit (a1100f0, #438), so this was never an introduced-at vs last-verified distinction, just an unverified number that review round 2 corrected in one place of three. 2.1.215 is the release the predicate was actually read off the shipped bundle, and it is the version installed here (`claude --version`, `@anthropic-ai/claude-code@2.1.215`). All three stamps now read 2.1.215. The stamp is load-bearing: the LLP sends a future reader to re-verify this undocumented, underscore-prefixed flag against a later release, and an ambiguous baseline defeats that mitigation. Regression tests fail on master and pass here: a round trip in test/core/client-detach-disk.test.js proving a non-string user value survives attach *and* detach byte-for-byte, plus attach-level coverage of boolean/number/null in test/plugins/claude-settings-attach.test.js. Co-Authored-By: Claude <noreply@anthropic.com> * claude undo: report a managed key the user overrode with a non-string The undo side of the same ownership rule this PR fixes on attach. The attach guard now decides ownership by the key being present rather than by the type of its value, so a hand-written JSON boolean at a managed key is a value the tree expects to meet. The undo's never-clobber notice was still type-gated: } else if (typeof current === 'string') so it stayed silent about exactly that value. The key correctly survived the detach, but the operator was never told a managed key had been left behind on disk - which is the whole job of the notice, since LLP 0045 promises "core accumulates one message for each key it left in place", and those keys stay on disk after a detach that otherwise reports success. The `json_path` undo in the same file already tests presence (`current !== undefined`); only the `json` marker branch diverged. Gated on `key in envObj`, not a bare `else`: a key the user deleted outright was not left in place and must not be reported. Both directions are pinned by tests - reverting the predicate fails "reports a managed key the user overrode with a non-string", widening it to `else` fails "stays silent about a managed key the user deleted outright". Doc-conformance only, so LLP 0045 is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * claude undo: the never-clobber notice tests own presence, everywhere Round 2 on the same ownership bug class, which now has three instances. 1. `detachLegacyJsonMarker` (the pre-record branch) still gated its notice on `typeof current === 'string'`. A legacy marker meeting an `ANTHROPIC_BASE_URL` the user had switched off with JSON's `false` or `null` left the key on disk - correctly - and said nothing about it. Reproduced: env before : {"ANTHROPIC_BASE_URL": false, "ANTHROPIC_API_KEY": "sk-x"} result : {"changed": true} <- no warning env after : {"ANTHROPIC_BASE_URL": false, "ANTHROPIC_API_KEY": "sk-x"} with the string case one line away reporting correctly. That is the same LLP 0045 promise the record-driven branch was fixed against last commit: "core accumulates one message for each key it left in place ... those keys stay on disk after a detach that otherwise claims success". The legacy branch is the one that most needs it, since it reverses by convention and therefore meets settings this tree never wrote. Reachable today: any pre-upgrade marker without a `managed` record still dispatches here. 2. The record-driven branch's new presence test is now `Object.hasOwn`, not `key in`. Its key names come off disk, from whatever `managed.env` a plugin's attach recorded, so an inherited `Object.prototype` name satisfied `in` and reported a key that was not on disk at all: marker managed.env : {"toString": "x"} settings env : {"ANTHROPIC_API_KEY": "sk-x"} result : "toString was overridden externally; leaving in place" which is precisely the false "left in place" report the presence test was introduced to prevent. The attach-side guard keeps `key in` correctly: its keys are in-tree literals, not disk input. Every predicate is pinned in both directions by mutation: reverting either branch to the type gate fails one test, widening either to a bare `else` fails another, and swapping `Object.hasOwn` back to `in` fails a third. No test fires for more than its own mutant. Doc-conformance only; LLP 0045 already states the rule, so it is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * claude attach: back up the base URL by presence, not by JSON type The fourth and worst instance of issue #448's bug class, on the one key the PR had not swept: `ANTHROPIC_BASE_URL` itself. Unlike the managed additions this key has no ownership guard to fall through, because attach always repoints it. The backup IS the guard, and it was type-gated: const liveBaseUrl = typeof env.ANTHROPIC_BASE_URL === 'string' ? ... : undefined so a hand-written non-string read as nothing-to-back-up. Attach then recorded the key in `managed.env` with no `prev_base_url`, and the core undo, finding a managed key with no prior to restore, deleted it. Reproduced end to end on 871016e, for `8080`, `false` and `null` alike: BEFORE ATTACH : {"ANTHROPIC_BASE_URL": false, "ANTHROPIC_API_KEY": "sk-x"} MARKER : prev_base_url absent, managed.env.ANTHROPIC_BASE_URL present AFTER DETACH : {"ANTHROPIC_API_KEY": "sk-x"} <- user's value gone result : {"changed": true, "removed": "http://127.0.0.1:4123"} No warning, no backup, nothing to recover from: strictly worse than the managed-additions bug this PR opened on, where the value at least survived on disk. `null` and `false` are the sharpest cases again, being how a user switches an override back off. Fixed across all three points the value passes through, since fixing any one alone still loses it: - attach takes the backup unconditionally (JSON cannot encode `undefined`, so `undefined` already means absent, and the existing `!== undefined` checks are the presence test the type test was standing in for), - re-attach carries the recorded prior forward on the field being present rather than on its type, - the core undo reads `prev_base_url` by presence, so a backup the marker is holding can no longer be discarded into the delete branch. The marker keeps the real JSON value; only the human-readable `prevValue` / `restoredValue` report coerces to a string, the pattern the undo already used for `removed`. LLP 0045 stated presence-not-type for "every env key attach adds beside the base URL" and left the base URL's own backup unstated, which is the gap that let this through. It now states the rule for the backup too, and why this key needs it more rather than less. Mutation-pinned in all three places: reverting the attach backup fails 8 tests, reverting the undo read fails the 3 round-trips, and reverting the re-attach read fails the two-attach round-trip. npm test: 2862 pass / 8 fail (the known leave-command baseline). typecheck clean. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: neutral-reconciler <neutral@example.com> Co-authored-by: Claude <noreply@anthropic.com>
Problem
With Claude Code attached to the HypAware gateway, a fresh session reports ~18% context after a one-word prompt; detached, the same setup reports ~4%.
The tokens are not the problem. Claude Code grants a native-1M model its 1M context window only when the
ANTHROPIC_BASE_URLhost isapi.anthropic.com(or_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URLis set). Behind any proxy it assumes 200k, so the same ~40k of startup context reads as 18% instead of 4%. It is not only cosmetic: context warnings and auto-compact fire against the wrong denominator and trigger far too early.Root cause
The claude attach writer (
hypaware-core/plugins-workspace/claude/src/settings.js) already compensates for one non-first-party default (ENABLE_TOOL_SEARCH, LLP 0045) but not for the assumed-window one, so pointing settings athttp://127.0.0.1:<port>silently cuts the assumed window.Fix
Attach also writes
env._CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL = "1", under the same ownership rule asENABLE_TOOL_SEARCH:_hypawaremarker recorded it as ours (so a re-attach keeps owning it),managed.envso the single core undo (detachClientFromDisk) removes exactly what attach added,The declaration is accurate: the gateway is a byte-transparent pass-through to
api.anthropic.com, which is what the flag asserts, so the other first-party gating it flips (traceparent propagation, the context-1m beta header, an extended usage-limit header, and Anthropic-direct channels like error reporting, org policy limits, and memory-sync eligibility) is correct rather than a tolerated side effect. It does not gate credential choice: the oauth beta header and bearer/API-key selection ride the active session independent of this flag, so the declaration sends no secret anywhere it was not already going. (Corrected post-review: an earlier version of this line claimed the flag also gates "oauth beta headers", which round 2 of review found false; the code comment and LLP 0045 were fixed then, this body line is fixed here as part of triage.)The two managed env additions are now one
MANAGED_ENV_ADDITIONStable plus a singlemanageEnvAdditionsownership helper, so a third key cannot drift from the undo record.LLP 0045 Part 3 gains the matching section (with the
@reffrom the code), including the honest caveat: the key is underscore-prefixed and undocumented (last verified against Claude Code 2.1.220), it fails soft, and the mitigation is to re-verify it if attached sessions start reporting an inflated percent again. The rejected alternative ([1m]model-name suffix) is recorded there too.Tests
Regression tests fail on
masterand pass with the fix:test/plugins/claude-settings-attach.test.jsenv._CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL = "1"and records it in the marker undo recordtest/core/client-detach-disk.test.jsVerified failing-then-passing: on the pre-fix source these run 7 failures across the two files; with the fix, 31/31 pass.
Local checks
npm test: 2796 pass, 8 pre-existing failures intest/core/leave-command.test.jsthat fail identically on unmodifiedmasterin this environment (environment-dependent, unrelated to this change).npm run typecheck: clean.npm run smoke -- claude_attach_detachfails on this branch and on unmodifiedmaster(pre-existing, on the SessionStart hook assertion), so it is not a regression from this change.Note
Already-attached machines pick the key up on the next attach (re-attach on endpoint drift, rejoin, or a manual
hyp attach); this change does not rewrite existing settings on its own.Fixes#437