Skip to content

claude gateway: retire the auto route — a turn runs where its model id says, and fails closed - #1198

Merged
gen16k merged 16 commits into
mainfrom
fix/1184-retire-the-auto-route
Sep 3, 2026
Merged

gen16k merged 16 commits into
mainfrom
fix/1184-retire-the-auto-route

Conversation

@gen16k

@gen16k gen16k commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Retires the Claude gateway's auto route and every automatic crossing between
Waired and the real Anthropic API. A turn now runs where its model id says, and
when the side it names cannot answer it fails with a reason Claude Code shows at
once.

Owner ruling: waired-ai/waired#1313, recorded in
docs/decisions/20260903/0333-no-automatic-crossing-to-or-from-anthropic.md
(merged as #1189) and its private twin.

What the gateway does now

  • A Waired id is served on a Waired node, or the turn fails with a reason.
  • An id the real Anthropic API serves passes through, errors relayed verbatim.
  • An id naming neither side is served here — relaying it would only buy a 404
    there, and it keeps the sentinel's claim exact: nothing but a turn carrying a
    real Anthropic model id leaves the machine.

Removed

routeAuto and the per-class route axis; internal/proxy/intercept/fallback.go
(the post-dispatch fallback recorder) and reroute_notice.go (the SSE notice,
#757); passthroughWithLocalDegrade's degrade branch (waired-ai/waired#665),
including for the auto-mode classifier; the agent-not-wired / degraded fail-open
passthrough; the X-Waired-Fallback, X-Waired-Fallback-Allowed,
X-Waired-TTFB-Budget-Ms and X-Waired-Min-Model-Size headers; last_fallback
and desired-claude-routing; waired claude route and the /waired-route
skill; the Stop hook waired claude _fallback-hook and its managed-settings
entry; the tray's main-3 / sub-4 route rows and its fallback note; the
Waired auto — 1M picker row; the statusline's fallback → Anthropic,
fell back → Anthropic and Waired-only segments.

X-Waired-Fallback-Reason and the tray's Recent activity rows are untouched:
those report Waired picking a different computer on your own mesh, which is a
different mechanism from the crossing this removes.

The fail-closed error shape

HTTP 400, with a message naming what could not answer and the two ways out.
Measured against Claude Code 2.1.259 (stub gateway, one status per run):

status requests sent what the user sees
400 1 API Error: 400 <our message> — verbatim, at once
401 3 prefixed Failed to authenticate.
403 1 prefixed Failed to authenticate.
404 2 our message discarded → "There's an issue with the selected model…"
409 3 verbatim
422 1 verbatim
429 3 rewrapped as a rate limit
503 3 verbatim, after the retries

400 is the only status shown at once, verbatim, with one request and no
misleading prefix — which also confirms rc5's O14 (waired-ai/waired#1309): a 404
throws our message away, so today's model_not_served and floor 404s lose their
text inside Claude Code.

A condition that clears by itself keeps its retryable shape: weights still
arriving, every peer momentarily at capacity, a runtime still installing stay
503 with a Retry-After. anthropicSelectionStatus decides the two sets once,
so the event ring records the status the client received (waired-agent#740).

Waits

waitPolicyFor split by leg instead of by the intercept's permission header:

  • a peer leg keeps the liveness watch and stays uncommitted, so its failure
    can still carry a status that names the peer;
  • a local leg is never aborted and writes an SSE keep-alive.

This closes a hole the header hid: a pinned peer leg previously got no wait at
all and hung silently. capacityQueueBudget is now always armed for the same
reason its own comment gives — every leg has nowhere else to go.

Fixes waired-agent#1180

A named peer row whose peer disappears now ends the turn at once with the peer's
name (PinnedPeerUnreachableError gained PeerName, resolved from the mesh
snapshot, grant pseudonym for a Public Share peer per spec §8.5), instead of ten
anonymous API error · Retrying 1/10 attempts.

Fixes waired-agent#1183

localRequest builds the local-inference request from an allowlist instead of
cloning: Content-Type, Accept, Accept-Encoding, anthropic-version,
anthropic-beta, x-claude-code-* and waired's own X-Waired-*. Everything
else — Authorization, x-api-key, Cookie, Proxy-Authorization,
User-Agent, and any header this build has not heard of — is not copied, so a
header added upstream is dropped by default rather than forwarded by default.
The passthrough leg stays byte-exact, as the gateway contract requires.

waired no longer sets Claude Code's default model

Owner ruling 2026-09-04, recorded in
docs/decisions/20260904/0146-waired-does-not-set-claude-codes-default-model.md,
which partially supersedes 20260828/0252 §4. §4 was safe only while a Waired
turn could still be carried to the real Anthropic API; with turns failing closed
a written Waired default would break every session on a computer with no engine
and no peer (rc5 had exactly such a host). A value already in the file is left
alone, and waired claude disable still removes one Waired wrote.

Inverted tests, each saying so at its site

  • TestWaitPolicyFor's "peer, no header (pinned route) → no wait" row.
  • claudemanaged: Write removes a retired Stop hook instead of installing one.
  • TestAnthropicMessages_PinnedPeerUnreachable…: 503 → 400, and Retry-After
    must now be absent (a fail-closed pin has nothing to wait for).
  • selection_error_test's contract table is now per-surface: the Claude side
    fails closed, the OpenAI side is unchanged (other clients, other retry rules).
  • The sentinel's escape signature: a degraded 2xx / masked 502 → an
    upstream-unreachable 502 on a leg that claims to be served locally.

Verification

Notes for the lanes waiting on this

Fixes #1184
Fixes #1183
Fixes #1180
Refs waired-ai/waired#1313
Refs waired-ai/waired#1312

🤖 Generated with Claude Code

https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

📘 Docs preview — the preview channel for this PR has been deleted now that it is closed.

@gen16k
gen16k force-pushed the fix/1184-retire-the-auto-route branch from 2f9346d to 2038132 Compare September 3, 2026 17:44
@gen16k

gen16k commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Real-hardware verification — sv-mag (linux, vLLM 0.28.0 / gpt-oss-20b)

Ran against this branch's build, installed over the host's binaries and the
daemon restarted. Everything below is verbatim.

The surfaces lost their route rows

waired claude status before / after the swap:

-fallback hook:      not installed
-main conversation:  auto
-subagents:          same as main  (auto — prefer Waired; visible fallback to Anthropic on failure)
+waired node:        auto (this device or a mesh peer)   (change with `waired worker`)

waired claude route auto → exit 1:

waired: `waired claude route` was removed: a turn runs where its model says, so
choose in Claude Code's /model — a Waired entry to run it on your computers, an
Anthropic model to run it on your Claude subscription. `waired claude status`
shows what the last turn did

The upgrade path cleans up after the retired mechanisms

Seeded managed settings with the retired Stop hook and an operator's own,
plus a stale ~/.claude/skills/waired-route/, then ran sudo waired claude enable:

=== hooks AFTER ===
{"SessionStart":[{"hooks":[{"command":"command -v waired >/dev/null 2>&1 && waired claude _models-cache write --from-managed --peer-entries 5 || true",…}]},
 "Stop":[{"hooks":[{"command":"/usr/local/bin/my-own-stop","type":"command"}]}]}
=== user model AFTER ===
"claude-waired-peer"          # unchanged
=== route skill AFTER ===
ls: cannot access '…/.claude/skills/waired-route': No such file or directory

waired's Stop entry gone, the operator's kept, the SessionStart hook untouched,
the stale slash command removed.

waired writes no default model

Removed model from ~/.claude/settings.json, ran waired claude enable again:

model removed:        "(absent)"
model AFTER enable:   "(absent)"

Picker cache after enable — four rows, and the retired 1M row is not among them:

claude-waired-auto
anthropic-waired-local
claude-waired-peer
claude-waired-peer-sv-macmini-local

A Waired turn runs on Waired

HTTP 200 in 0.267s   # engine ready here
HTTP 200 in 3.700s   # local inference OFF — a peer took it
⚡ waired: on Waired (peer sv-macmini.local: qwen3.5-9b)

The second one is worth noting on its own: with this computer's engine off, the
any-node row was answered by a peer and the footer named the peer and the
model. That is the attribution path #1042/#602 built, unchanged.

… and fails closed when no node can

Local inference off and waired worker set --mode=local-only, so nothing on
Waired can take it:

HTTP 400 in 0.0008s
{"type":"error","error":{"type":"waired_inference_disabled","message":"Local inference is turned off on this host. Pick an Anthropic model in /model to send this turn to the cloud, or run `waired doctor` to see what is missing."}}

Through a real claude binary, same state — shown at once, verbatim, no retry
loop:

API Error: 400 Local inference is turned off on this host. Pick an Anthropic
model in /model to send this turn to the cloud, or run `waired doctor` to see
what is missing.

waired-ai/waired#665 is gone

0.0.0.0 api.anthropic.com in /etc/hosts, daemon restarted so no pooled
connection survives, then a turn naming claude-opus-5:

HTTP 502 in 0.0007s
{"error":{"message":"waired proxy could not reach the upstream API: dial tcp 0.0.0.0:443: connect: connection refused","type":"waired_upstream_unreachable"},"type":"error"}

Not answered by a local model. The response carries no X-Waired-* header at
all
, and the daemon recorded the side correctly:

{"last_request_model":"claude-opus-5","last_request_route":"anthropic",…}

— no policy, no last_fallback in the payload any more, and
last_request_route is what the routing sentinel asserts on
(docs/decisions/20260829/1655).

/etc/hosts restored; api.anthropic.com resolves normally again.

Not covered here

  • waired-agent#1180's own repro (a named peer row whose peer stops sharing)
    needs a second machine to toggle sharing on, and sv-macmini is held by another
    lane. The error shape and the peer naming are covered by unit tests
    (PinnedPeerUnreachableError.PeerName, selection_error_test.go); the live
    repro is queued.
  • A pre-existing display gap this made easier to see: the footer's peer half
    reads the mesh's reachability but not the waired worker mode, so with
    --mode=local-only and the engine off it still rendered
    ⚡ waired: on Waired (peer …) while the turn in fact failed closed. Same
    claim on the old auto branch, so this branch does not introduce it — but the
    red row is now the only warning, which makes it worth its own issue.

Host left on: this branch's build (0.0.0-dev), engine ready, inference on,
sharing on / public off, /etc/hosts restored, managed settings holding only
the SessionStart hook.

@gen16k
gen16k force-pushed the fix/1184-retire-the-auto-route branch 2 times, most recently from 40b0f9d to 9188ae0 Compare September 3, 2026 18:35
gen16k added a commit that referenced this pull request Sep 3, 2026
… it (#1202)

`WairedSetup-<version>-x64.exe` is attached to every release and every edge
prerelease, and it is a full install path: it elevates, registers the service,
starts it, and turns on the Claude Code integration by default. The site
described one route, the `iwr … | iex` line, and did not name the setup program
anywhere until #1199.

The gap had already produced two wrong promises. packaging/windows/ sat outside
docs-surface-guard's surface list until #1199 added it, so the GUI installer's
behaviour could change with no docs page obliged to keep up — which is how
getting-started/update.mdx came to promise a fresh-install behaviour only
install.ps1 had (#1181). And every release's notes tell people to "see
SmartScreen note in docs" for a note that did not exist; SmartScreen appeared
nowhere under docs-site/.

The new section follows the order a person downloading it actually meets: no
`latest/download/…` shortcut, because the filename carries the version;
"Windows protected your PC" and the way through it, which is why the page still
leads with the PowerShell line; the permission prompt; a wizard that does not
ask where to install and has one checkbox; that it does not run `waired init`,
so signing in is still ahead of you; the log it always writes; in-place upgrade
and uninstall, including that the state directory is kept by default; and the
unattended switches with exit 0 / exit 7.

The checkbox is described rather than quoted: its on-screen text still promises
a transparent fallback to the Anthropic API, which #1198 is retiring, and a
verbatim quote would go stale the day that lands.

Also widen the "What gets installed" Start Menu row — both installers create
**Waired** and **Waired (CLI)**, and the table listed one.

Fixes #1202
Refs #1181, #1199, waired-ai/waired#1312

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JQqxQSWDFbQauMDuCbJ2
gen16k and others added 16 commits September 4, 2026 04:08
A turn now runs where its model id says. A Waired id is served on a Waired
node or fails with a reason Claude Code shows at once; an id the real
Anthropic API serves passes through with its errors intact; an id neither
side owns is served here, so nothing but a turn naming a real Anthropic
model leaves the machine.

Removed: the auto route and its post-dispatch fallback (fallback.go), the
SSE reroute notice (reroute_notice.go), the anthropic-route local degrade
(waired-ai/waired#665), the agent-not-wired fail-open passthrough, the
X-Waired-Fallback / -Fallback-Allowed / -TTFB-Budget-Ms headers,
last_fallback and its per-class routing policy, `waired claude route`, the
/waired-route skill, the tray's route rows and the Stop hook that announced
a fallback.

The wait policies stay and split by leg instead of by permission: a peer
leg keeps the liveness watch and stays uncommitted so its failure can name
the peer, a local leg writes SSE keepalive and is never aborted.

waired also stops setting Claude Code's default model (owner ruling
2026-09-03): with fail-closed turns, a written Waired default would make
every session fail on a computer with no engine and no peer.

Refs #1184, #1183, #1180, waired-ai/waired#1313

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Rewrites the suites the removal inverted, each row saying so:

- intercept: the three dispatch rows (an Anthropic id leaves, a Waired id
  does not, an id naming neither side is served here), the fail-closed 400
  naming its way out, and the picker table without the retired 1M row.
- gateway: waitPolicyFor split by leg instead of by the intercept's
  permission header, and the selection-error table split by surface — the
  Claude side fails closed with a 400, the OpenAI side is unchanged.
- claudemanaged: Write now REMOVES a retired Stop hook instead of
  installing one.
- claudecode: only the removal of the /waired-route skill and of the
  default model remains.
- e2e sentinel: the escape is now an upstream-unreachable 502 on a leg that
  claims to be served locally; the degrade-header rows are gone with the
  degrade.

Also keeps the ratified minimum-model-size phrasing (waired-agent#1128,
"a large model", never "a large model or larger") alive at its new site,
the gateway's fail-closed message, since the reroute notice that used to
carry it is gone.

Refs #1184, #1183, #1180

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Rewrites every page that described the auto route or the automatic
fallback, English and the ja mirror: the "Which AI answered?" table from
the status line's own strings, "Switch where requests go" as /model rather
than /waired-route, the privacy page's "one place a cloud fallback exists",
the CLI reference's route block and --min-model-size reroute, the
troubleshooting sections about the ten-minute backstop and a pinned
computer, the tray's Claude Code submenu, the glossary's "Falling back",
the doctor sample, the landing diagram, and the first-run boxes. Adds a
troubleshooting section for the new fail-closed error.

Records the owner ruling that waired no longer writes Claude Code's
default model (20260904/0146), which 20260828/0252 §4 had required: that
was safe only while a Waired turn could still be carried to the real
Anthropic API, and would now fail every session on a computer with no
engine and no peer.

Also makes four product strings true again — "requests go to your other
computers or the cloud" no longer describes what happens — and unifies the
two spellings of the fail-closed message's tail.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
… turn

The routing block lost its policy rows with the routes, and took the
last-served and waired-node lines with them by accident — the two that
answer "where is my work actually going" now that the side is the model
id's to decide and the node is `waired worker`'s.

Also drops the dead code the removal left behind, and documents the rows.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Measured against Claude Code 2.1.259 with a stub gateway, one status per
run: 400 is the only one shown at once, verbatim, in a single request and
without a prefix. 404 discards the message and substitutes "the selected
model may not exist" — which is what rc5's O14 saw — and 503 adds a
server-side diagnosis of its own after three retries.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Two things the default build never compiles, and so never checked:

- internal/e2e/integration is behind `-tags integration`, where the
  harness still named state.ClaudeRouteAnthropic and the deleted
  fallback header. It takes its "the turn went upstream" vocabulary from
  claudecode.RouteAnthropic now, and the failure block no longer tries to
  recover a status nothing discards.
- installtest-windows.ps1's #787 contract row required the Stop hook to
  be written as the bare Windows command. The hook is retired, so the row
  is inverted — it must be absent — and the SessionStart hook that keeps
  the /model picker current takes its place as the entry under test.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
X-Waired-Fallback-Allowed and X-Waired-TTFB-Budget-Ms had one reader each
left, and both were about a crossing that no longer happens.

capacityQueueBudget read the first to keep the auto leg failing fast: a
turn with somewhere else to go must not wait for a busy peer's slot. No
turn has somewhere else to go now, so the exemption goes and every leg
waits — which is what that function's own comment always said about a leg
with no escape. Its "auto route never queues" rows are inverted.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
The install-time consent text still said local inference "falls back to
the real Anthropic API when unavailable, so claude never breaks". It does
not, and saying so is the sentence a user would act on. It now names the
choice that replaced it: /model picks where each turn runs.

Also two comments that still described the machine-wide route policy.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
`waired link --help`'s Claude Code section and doctor's engine-not-ready
detail both told the reader that requests fall back to the real Anthropic
API. They do not. Both now name what actually happens: /model decides the
side, and a Waired turn with no node to take it fails.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
The CLI reference (and its ja mirror) said pausing put your tools back on
the cloud. It stops this computer answering; a turn addressed to Waired
then goes to another of your computers, or fails.

Refs #1184

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FKTcq342KkitvJxaviMdR
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
The rebase over #1195 left doctor.mdx and troubleshooting.md's ja mirrors
carrying a hash computed against the pre-rebase English page. i18n:check
is green again.

Refs #1184

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjQDeCzecmEYvCZvnw9R9f
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
The router's text is a diagnosis for a log, and the mesh wrapper appends
the model id after the peer, so the message a user reads ended as two
quoted values run together: `… is unreachable: "sv-mag": "gpt-oss-20b"`.
Measured on sv-mag with a real Claude Code turn.

This is the one error whose whole point is that a person can act on it
(waired-agent#1180), so it now says which computer, in words.

Refs #1180, #1184

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjQDeCzecmEYvCZvnw9R9f
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
waired-agent#1200 moved the ruled phrasing into router.ModelSizePhrase and
gave SizeFloorError its own sentence, so the copy this branch kept alive
after deleting the reroute notice is redundant. The gateway now passes the
router's text through and adds only the exit specific to a floor miss —
the setting to change.

Also re-accepts the ja cli.md pair: main's #1164/#1207 edited the English
page while this branch held the translation hash.

Refs #1184, #1128, #1200

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjQDeCzecmEYvCZvnw9R9f
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Refs #1184

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjQDeCzecmEYvCZvnw9R9f
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Two places outside Go and docs-site that this branch's sweep missed, both
found by the L96 lane:

- waired-setup.iss's [Tasks] description is the checkbox text in the GUI
  installer's wizard, and it promised that Waired "transparently falls
  back to the real Anthropic API". It says what decides now instead.
- installtest-windows.ps1 drove `waired claude route` as its proof that
  an allow-list-exempt read reaches the daemon over the named pipe. That
  command no longer contacts the daemon, so the probe would have kept
  passing while proving nothing. It drives `waired claude status` now,
  and asserts a line that is only printed once the read succeeded.

Refs #1184

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjQDeCzecmEYvCZvnw9R9f
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Refs #1184

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjQDeCzecmEYvCZvnw9R9f
Signed-off-by: gen16k <gen16k@users.noreply.github.com>
gen16k added a commit that referenced this pull request Sep 5, 2026
…the page (#1215)

Every ja page carried a sourceHash in its frontmatter — a digest of the English
page it was translated from — and i18n:check compared it. That is a derived
value stored in a versioned file: two PRs touching one English page always
rewrote it to two different values and always conflicted, on that line and
nothing else, while the prose merged cleanly.

Measured on main over the 60 days before this: 219 of 622 commits touch a ja
page, 90% of them rewrite the hash, and 35 of 144 overlapping PR pairs share a
ja file. At one or two docs lanes that is a cost per collision. At today's lane
count it is a condition on landing — #1198 held 12 of the 32 ja pages for 2 h
19 m and went CONFLICTING four times, each within nine minutes of a competing
PR merging, and the answer of the day was to hold everyone else's merges. It is
not only wide docs PRs: #1237, a gateway change that touches three docs pages
incidentally, spent two of its four force-pushes on the same line. This PR
itself went CONFLICTING 2 m 40 s after it was opened, before its own CI
finished, on one line of ja/troubleshooting.md.

Removing the stored value removes the class. The freshness question moves to
the diff, where nothing is stored and nothing conflicts: a PR that changes an
English page changes its Japanese one too, enforced by
scripts/ci/i18n-pair-guard.sh in docs-guard.yml, with a
`translation-not-needed: <reason>` line in the PR body for the English edit
that genuinely needs no translation. Replayed over every commit since the
mirror was enforced (#147), that line would have been needed six times across
405 page pairs — three terminology sweeps where an English phrase changed and
the Japanese sentence was already right — and nothing else in the repository's
history would have failed the rule.

A merge driver was considered and does not work: a custom driver only runs
where git config sets it, and GitHub computes mergeability server-side, so the
PR still goes DIRTY and still has to be rebased.

i18n:check keeps the two questions that are about the tree and cannot be
answered from a diff: is there a ja page at all, and do the two sides still
have the same shape (#678, #1011). That comparison now runs on every pair,
always, instead of only on pairs whose hashes already agreed — and that gate
was worth less than it looked, because on the path a stale pair actually takes,
--accept wrote the hash before it ever reached the refusal, recording a lost
heading as intentional and reporting it only on the next run.

--accept and i18n:accept are gone with the value they wrote. So is the "may be
out of date" notice in PageTitle.astro and the second copy of the digest it
carried: deploy-docs.yml runs i18n:check before the build in the same job, so a
page the notice would fire on never reaches the site, and it has never rendered
on docs.waired.ai since it was added alongside the check in #147.

The rule and its opt-out are written where a contributor already reads about
the sibling one — CONTRIBUTING.md next to docs-not-needed:, and CLAUDE.md
§Documentation, whose "npm run i18n:check gates it" was true of freshness
before this change and is not any more.

Sequenced directly with the lanes that had docs PRs open (#1235, #1237 first;
#1241 after), because this touches all 32 ja pages at once.

Fixes #1215

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BgrcRiYj339QxzD3b6KijQ
gen16k added a commit that referenced this pull request Sep 6, 2026
Three residuals of the /model restructure, all found reading the
surrounding code during L97.

CLAUDE_CODE_MAX_CONTEXT_TOKENS was written only when this host resolved a
local window, so a computer with no engine wrote nothing at all — and
Claude Code then assumed its own 200k default AND showed "isn't described
by this version's model catalog" on every Waired row, which is the notice
the variable exists to suppress. Every row such a host offers is a peer
row, so a peer's window is not an approximation of something better; it is
the only honest number available. WriteOptions and RemoveOptions gain a
PeerContextWindow used only when the local one is 0, and both go through
DeclaredContextWindow() so a scrub recognises exactly what a write would
have put there.

Smallest reachable window, not largest: the number sizes one session and
the rows it covers are several computers, so over-declaring means a turn
is compacted only after the gateway has already refused it. Nothing
reachable still writes nothing — declining beats guessing about what an
elevated process tells Claude Code.

The general "one number for rows that hold different windows" question is
settled as leave it, recorded rather than changed: the variable is a
compaction hint, and what actually refuses an over-long prompt is this
gateway's own 400.

KindClaudeNodeChange and KindClaudeNodeFallback have had no producer since
#1198 removed the anthropic-route degrade and the per-class routes. Both
repositories were checked for readers: none in code, two mentions in the
private dev-docs, which follow separately.

Fixes #1246


Claude-Session: https://claude.ai/code/session_01HJUUSmAfbRdjER1seDdjXm

Signed-off-by: gen16k <gen16k@users.noreply.github.com>
Co-authored-by: gen16k <gen16k@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment