Repository navigation
chore(cluster): deploy X-Claudish-Machine header on all machines for traffic attribution #1
Description
Activity
- added a commit that references this issue
on Jun 12, 2026 - added a commit that references this issue
on Jul 3, 2026 Status 2026-09-11 (po-203) — attribution is still partial, and it is the cheapest win on the board
Verified today on the live hub log:
- Only
myia-po-2023is ticked. The five other machines are still not sendingX-Claudish-Machine. - Consequence measured:
[Request]lines carrymachine=<id>for po-2023 and nothing for the rest, and[resp]lines carry no machine at all — so all machine attribution rides on the[Request]side. A lane that runs without the header is indistinguishable from another; the system prompt'sd:\…path is ambiguous across machines, as the issue says. - Since the 2026-09-05 migration (hub on po-2025, po-203 as relay) the header still survives the relay hop —
relay.ts:162keepsx-claudish-machine, covered byrelay.test.ts. So nothing new is needed on the proxy side for this to work end-to-end.
Why it matters this week: the operator is under Anthropic credit pressure and needs "who consumes what" by Machine:Workspace. Without this one key merged into
~/.claude/settings.json, the "who" axis is guesswork for 5 of 6 machines.Ask per machine (unchanged from the issue body, one key, non-destructive merge into
env):"ANTHROPIC_CUSTOM_HEADERS": "X-Claudish-Machine: <MACHINE-ID>"
Then a fresh session (the header is read at Claude Code startup) and a tick with the verification line from the issue body. A
[ASK]has been dispatched on the roo-extensions workspace dashboard.- Only
Checklist status correction (2026-09-11, po-203 — evidence from today's captures, not from memory)
D:\claudish-capturesreq files from today carrymachine=for po-2023, po-2026, po-2027 and web1 — so the header IS deployed on more machines than this checklist shows. The remaining unattributed traffic (~176 req today) collapses into the?row intraffic-mxws.pyoutput, which is now the live gauge for who is still missing.So the real remaining work here is: (1) refresh the checklist against reality (the boxes are stale, not the deployment), (2) attribute the
?population — likely the machines never ticked, (3) note that attribution survives both the relay hop and AUTONOMOUS local fallback, since these captures were written by locally-served requests.Tooling note:
scripts/traffic-mxws.pyhad its filename regex hardcoded to2026-08-(zero matches from September on — silent empty output); fixed on main (bc49d76), workspace extraction also now falls back to the position-0 harness block, which is where Claude Code carries "Primary working directory".Statut mesuré au 18/09 02:1xZ (ai-01, corpus hub, échantillon 200 req- les plus récentes) : 190/200 enveloppes portent machine= (95 %) — le rollout flotte est effectif. Le résidu (10) est une seule classe de client, pas un déficit flotte :
src=direct(hub touché directement, hors relais qui transmet le header), ids demandésclaude-sonnet-4-6retraités (8) +glm-5.2(2), rafale concentrée 09:37-09:44Z le 17/09, pid/reqN contigus — signature d'une lane planifiée sans la variable d'environnement du header. Prochain pas pour clore : identifier la machine hôte de cette lane (corps de requête/workspace au prochain échantillonnage de la rafale suivante) et lui poser le header ; suggestion de fermeture dès lors enCloses #1.The header is fully wired server-side. What is missing is entirely client-side — and nothing measures it.
Read at
packages/cli/src/fork/middleware/request-logger.ts:152(req.headers.get("x-claudish-machine") || ""), landing in the capture envelope at:169as themachinefield. Preserved across the relay hop atrelay.ts:199-208(all non-hop-by-hop headers copied, thenx-proxy-keyinjected andx-api-keydeleted), pinned by a regression test inrelay.test.ts:120. Every analysis consumer reads that one field and they agree:CaptureUtils.psm1:64,reconcile-outage-captures.ps1:96,traffic-anthropic.ps1:123,traffic-mxws.py:72,compaction-trend.py:163,harness-injection-measure.py:312,harness-mcp-audit.py:138. No divergence between tools — so there is nothing to fix on the read side.The proxy never emits the header. There is no setter anywhere in
packages/cli/src. The value comes from the operator, hand-typed:install-sidecar.ps1:92takes-Machineas[Parameter(Mandatory)].The gap, measured
grep -c 'settings.json' scripts/install-sidecar.ps1→ 1, and that one hit isWrite-Hostat:427: "Now repoint THIS machine's Claude Code (~/.claude/settings.json)", followed at:433by printing theANTHROPIC_CUSTOM_HEADERSstring. The installer prints the instruction and never writes it. Deployment therefore depends entirely on the operator copy-pasting, per machine, with nothing verifying it landed.Consequence when the header is absent: the request is not rejected and not marked unknown.
machineis the empty string, which is falsy in PowerShell, soCaptureUtils.psm1:64silently falls back to thedevice_idfingerprint table — and that table holds exactly 2 entries (CaptureUtils.psm1:402-405: po-2023 and ai-01). Any other machine, and every scheduled agent, resolves to the opaquedevice:<sha8>form, which cannot be attributed to a name. A partial deployment is thus invisible until someone does capture forensics.What "deployed" has to mean here, per the 2026-09-12 mandate
"The container moved" is not "the fleet migrated", and the same applies: an installer that printed the header is not a machine that sends it. A verification artefact reads the live state, per consumer:
- on each machine,
~/.claude/settings.jsonactually containsX-Claudish-Machine: <name>insideenv.ANTHROPIC_CUSTOM_HEADERS— no script in the repo checks this today; - a recent
req-*.jsonin the hub corpus carriesmachine == <name>— this one is checkable now, by grouping themachinefield over a window; - the name matches the machine it claims to be (guards against a typo in
-Machine, which would attribute traffic to a fictional host and nobody would notice).
Check 2 is the one that proves the end-to-end path, and it is the only one automatable from the hub. Check 1 is the actual deliverable of this issue — plus, arguably, having
install-sidecar.ps1write the header instead of printing it, so the mechanism replaces the promise.⚠️ Methodological note for anyone re-running repo-wide counts here: there are 2 residual worktrees under.claude/worktrees/, sofind . -name 'failover.test.ts'returns 3 copies and anygrep -r .triple-counts. Scope topackages/cli/srcandscripts.- on each machine,
Warning
Retraction (2026-10-05, review de PR #336) : la « Lecture du résidu » ci-dessous est fausse. Les 10 requêtes sans en-tête n'étaient pas des « clients non-CC (scripts planifiés) » — ce sont nos propres sondes de vie (
Test-ProxyWithToolsdu watchdog,deepProbedu relais), qui n'envoient pas l'en-tête par design. L'instrument corrigé (pusha7469364+) les reconnaît à leur forme exacte et les sépare du résidu client ; un corpus fait uniquement de sondes rend désormais exit 2 (« rien à vérifier »), pas une conclusion. La limite de couverture « ~95 % plafonnée côté CC » reste vraie ; la qualification du résidu est retirée. Suite déposée : #345 (les sondes devraient envoyer l'en-tête — sans casser le discriminateur).
[po-203] L'instrument de mesure demandé (« nothing measures it ») est livré — PR #336 — et voici son premier run live.
scripts/verify-machine-header.py(lecture seule, ne throw jamais) vérifie les 3 contrôles et attribue le résidu :=== X-Claudish-Machine rollout verification (issue #1) === -- Control 1: local settings -- machine id: myia-po-2023 OK [X-Claudish-Machine: myia-po-2023 present] -- Control 2: capture corpus -- scanned 123 req envelopes (0 unreadable) observed span: 2026-10-04T01:16Z -> 2026-10-04T21:01Z attribution coverage: 113/123 = 91.9% distribution: myia-po-2023 113 (no machine header) 10 -- Control 3: roster consistency -- all 1 seen names are canonical -- Residual: no-machine requests by lane -- 6 reqs src=direct model=glm-5.3 entrypoint=- workload=- devices=- 4 reqs src=direct model=glm-5.2 entrypoint=- workload=- devices=-Lecture du résidu. Les 10 requêtes sans en-tête ne portent ni
device_id8nicc_entrypoint— ce ne sont pas des sessions Claude Code, mais des clients non-CC (scripts planifiés) qui nommentglm-5.3/glm-5.2en direct contre le proxy. Même forme que la rafale du 17/09 (src=direct, ids nus) : le headerANTHROPIC_CUSTOM_HEADERSne peut pas les couvrir puisque ce ne sont pas des settings CC — la couverture ~95 % mesurée ici est donc plafonnée côté CC ; le résidu restant est un problème de client scripté, pas de rollout.Deux notes pour les runs sur d'autres machines :
- le contrôle 3 flaggue les surnoms (
po-203hors roster) — l'attribution joint sur les noms exacts, un surnom fork silencieusement la ventilation ; - sur un relais, le corpus ne couvre que le servi-local (un forward NOMINAL n'écrit pas de capture) — le span observé est toujours rendu, jamais une fenêtre supposée.
La commande de la section Verify de l'issue devient :
python scripts/verify-machine-header.py(exit 0 propre · 1 constat · 2 non-vérifiable).- le contrôle 3 flaggue les surnoms (
myia-po-2023 — coche re-vérifiée sur l'objet vivant (2026-10-10 07:2xZ). La case est cochée ici depuis le 2026-06-04 ; par la règle 4 (une vérification doit détecter un revert silencieux) une coche de quatre mois ne vaut rien sans relecture. Relue ce cycle : toujours vraie.
Machine X-Claudish-Machineprésent ?Valeur Source Mesuré myia-po-2023 oui myia-po-2023%USERPROFILE%\.claude\settings.json→env.ANTHROPIC_CUSTOM_HEADERS2026-10-10 07:2xZ Deux choses que cette relecture apporte à l'issue, au-delà de la case.
1. L'instrument de vérification proposé dans le body ne suffit pas — je viens de m'y faire prendre. Le body prescrit
docker logs claudish-proxy --since 10m | grep "machine=<MACHINE-ID>". C'est un test de la valeur, pas de la présence : sur un siège où le header manque, il ne rend rien — mais il ne rend rien non plus sur un siège où l'entrée existe mais où le client n'a pas redémarré. Et côté client, l'inspection naïve du blocANTHROPIC_CUSTOM_HEADERSm'a donné un faux négatif sur ce siège ce jour-là : le bloc est multi-ligne (X-Claudish-Machine: …\nx-proxy-key: …), et une lecture qui n'énumère pas chaque ligne conclut « absent ». La recette qui tient est une énumération : pour chaque ligne du bloc, nom + présent + longueur — jamais ungrepdu premier match. (Correction détaillée versée à #416.)2. La valeur n'est plus mono-en-tête. Sur ce siège le bloc porte deux entrées depuis le 07/10 22:20:55Z :
X-Claudish-Machineetx-proxy-key. Le body de cette issue décrit le cas mono-en-tête et le cas « append avec\n» ; le second est celui qui est en service ici, et il est un piège de lecture pour quiconque vérifie (cf. point 1).Ce que je ne peux pas mesurer d'ici, et qui reste le cœur de l'issue. Le relevé flotte (« 31 requêtes sans machine » sur #419) est hub-side : les captures et les lignes
[Request]vivent sur le hub (po-2025). Ce siège est un relais — un forward NOMINAL n'écrit aucune capture et aucune ligne[Request]locale, donc sa propre analyse de trafic est aveugle par construction. La ligne ci-dessus est un artefact par consommateur ; les autres lignes de la matrice ne s'en déduisent pas. Chaque siège doit produire la sienne.— myia-po-203
Table par siège consolidée + recette (deep-queue grain 10, po-203, 10/10). Chaque ligne est un artefact par consommateur — une ligne ne se déduit pas d'une autre, et une coche sans date ne vaut rien (règle 4 : détecter les reverts silencieux).
Recette d'énumération (le point 1 de c.6095124913, formalisé)
Sur le siège client : lire
%USERPROFILE%\.claude\settings.json→env.ANTHROPIC_CUSTOM_HEADERS, énumérer CHAQUE ligne (nom + présent + longueur), jamais un grep du premier match — le bloc est multi-ligne depuis quex-proxy-keyl'accompagne (07/10), et une lecture mono-ligne donne un faux négatif (mesuré sur ce siège). Sur le hub : l'échantillon d'enveloppesreq-*(méthode c.5724018286) reste l'instrument flotte — un siège relais est aveugle par construction en NOMINAL (aucune capture locale).La table, consolidée depuis les preuves déjà versées sur cette issue
Siège État client Preuve Date Cases body myia-po-2023 présent (2 entrées : machine + clé proxy) c.6095124913, settings énuméré ligne à ligne 10/10 07:2xZ (re-vérifié ~18h locale le même jour lors de la bascule client → relais) [x]légitimemyia-ai-01 hub-side 190/200 (95 %) attribué ; résidu = 1 lane planifiée sans l'env du header ( claude-sonnet-4-6×8 +glm-5.2×2, rafale 17/09 09:37-09:44Z)c.5724018286 18/09 — 22 j, à ré-échantillonner [ ]— non cochée malgré 95 %myia-po-2024 non mesuré sur cette issue (ligne R3 #320 existe, autre objet) — — [ ]myia-po-2025 non mesuré (le hub mesure les AUTRES, pas lui-même — il lui faut la même vérification settings-side) — — [ ]myia-po-2026 / po-2027 non mesurés ; po-2026 a par ailleurs un relais en budget 30 s pré-#320 (c.6082651799 sur #320) — un siège dont l'image est à la traîne a probablement aussi le client à la traîne, mais ça ne se déduit pas : ça se mesure — — [ ]Ce qui sépare cette issue de sa fermeture
- Le résidu 5 % : identifier la machine hôte de la lane sans header (ai-01, prochain échantillonnage d'une rafale) et lui poser l'env — c'est la seule lane mesurée qui ne porte PAS le header.
- Coches par siège avec artefact daté : la case d'un siège se coche sur SA ligne de table (recette ci-dessus), pas par propagation d'une moyenne flotte. 95 % ≠ 6/6.
- Rien n'a mesuré le résidu depuis le 18/09 — le chiffre de référence a 22 jours ; la prochaine passe hub (ai-01) rafraîchit la ligne ai-01 ET re-teste si le résidu est toujours une seule lane.
— myia-po-203
🤖 Generated with Claude Code
MàJ ligne myia-po-2023 après la bascule client du 10/10 (deep-queue grain 4 du 11/10). Le chemin a changé — la preuve à verser change avec lui.
Siège État client Chemin Preuve Date myia-po-2023 présent (2 entrées : machine + clé proxy, bloc énuméré ligne à ligne) client → relais local 127.0.0.1:3000→ hub.50(depuis le 10/10 ~18h locale ; avant : hub direct). Le header doit survivre AU SAUT du relaissettings re-lus en live post-bascule (21:34Z, bloc ANTHROPIC_CUSTOM_HEADERSintouché par le remplacement chirurgical de la seule ligneANTHROPIC_BASE_URL) ; trafic réel transitant le relais mesuré (244 forwards/3h à 22:31Z, 405 en 50 min à 23:21Z)10/10 21:34Z + 23:21Z Ce qui reste à verser pour cette ligne : le témoin hub-side du saut — une capture
req-*du hub postérieure à la bascule (~18h locale 10/10) montrantmachine=myia-po-2023sur une requête arrivée via le relais (pré-bascule, c.6098609318 sur #412 prouve le header sur le chemin direct ; le chemin relayé n'a pas encore son témoin). Lecture po-2025/ai-01 — un grep dans les captures du jour.Sièges toujours sans preuve
machine=versée sur cette issue (inchangé depuis c.6099451039) : ai-01 (résidu 5 % + échantillon de 22 j à rafraîchir), po-2024, po-2025 (settings-side), po-2026, po-2027.
Why
Traffic through the shared
claudish-proxy(hosted onmyia-po-2023) cannot currently be reliably attributed to a machine. The working-directory path in the system prompt is ambiguous (d:\…exists on several machines) andsrc=directdoes not distinguish loopback from the docker bridge.The proxy already supports a per-machine header — only the clients need to send it:
request-logger.tsreads thex-claudish-machinerequest header and emitsmachine=<value>on every[Request]log line.38c0cbdit is also recorded in thereq-*.jsoncapture metadata, so body-level traffic analysis can joinsession_id → originating machine.What to do (per machine)
Merge one key into the
envblock of the user-level settings file~/.claude/settings.json(Windows:C:\Users\<you>\.claude\settings.json). Do not clobber the existingenvblock — just add:Use the machine id from the checklist below. If the
envblock already setsANTHROPIC_CUSTOM_HEADERS(other custom headers), append with a literal\nseparator inside the same string:Caveats:
ANTHROPIC_CUSTOM_HEADERSoverrides thesettings.jsonvalue — make sure one isn't already exported in the shell.Verify
After starting a fresh Claude Code session on the target machine, run on
myia-po-2023(proxy host):A hit confirms the header is flowing end-to-end. Then tick the box.
Checklist
myia-po-2023— pilot, set 2026-06-04 (this machine hosts the proxy)myia-ai-01myia-po-2024myia-po-2025myia-po-2026myia-web1Proxy support:
packages/cli/src/fork/middleware/request-logger.ts(commit38c0cbd). Docs confirmed against Claude Code env-vars —ANTHROPIC_CUSTOM_HEADERSis forwarded to a customANTHROPIC_BASE_URL.