Skip to content

chore(cluster): deploy X-Claudish-Machine header on all machines for traffic attribution #1

Description

@jsboige

Why

Traffic through the shared claudish-proxy (hosted on myia-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) and src=direct does not distinguish loopback from the docker bridge.

The proxy already supports a per-machine header — only the clients need to send it:

  • request-logger.ts reads the x-claudish-machine request header and emits machine=<value> on every [Request] log line.
  • As of commit 38c0cbd it is also recorded in the req-*.json capture metadata, so body-level traffic analysis can join session_id → originating machine.

What to do (per machine)

Merge one key into the env block of the user-level settings file ~/.claude/settings.json (Windows: C:\Users\<you>\.claude\settings.json). Do not clobber the existing env block — just add:

"ANTHROPIC_CUSTOM_HEADERS": "X-Claudish-Machine: <MACHINE-ID>"

Use the machine id from the checklist below. If the env block already sets ANTHROPIC_CUSTOM_HEADERS (other custom headers), append with a literal \n separator inside the same string:

"ANTHROPIC_CUSTOM_HEADERS": "X-Existing: value\nX-Claudish-Machine: <MACHINE-ID>"

Caveats:

  • A real shell environment variable ANTHROPIC_CUSTOM_HEADERS overrides the settings.json value — make sure one isn't already exported in the shell.
  • The header is read at Claude Code startup, so it takes effect on the next session, not the running one.

Verify

After starting a fresh Claude Code session on the target machine, run on myia-po-2023 (proxy host):

docker logs claudish-proxy --since 10m | grep "machine=<MACHINE-ID>"

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-01
  • myia-po-2024
  • myia-po-2025
  • myia-po-2026
  • myia-web1

Proxy support: packages/cli/src/fork/middleware/request-logger.ts (commit 38c0cbd). Docs confirmed against Claude Code env-vars — ANTHROPIC_CUSTOM_HEADERS is forwarded to a custom ANTHROPIC_BASE_URL.

Activity

  1. jsboige commented on Sep 11, 2026

    @jsboige
    OwnerAuthor

    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-2023 is ticked. The five other machines are still not sending X-Claudish-Machine.
    • Consequence measured: [Request] lines carry machine=<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's d:\… 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:162 keeps x-claudish-machine, covered by relay.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.

  2. jsboige commented on Sep 11, 2026

    @jsboige
    OwnerAuthor

    Checklist status correction (2026-09-11, po-203 — evidence from today's captures, not from memory)

    D:\claudish-captures req files from today carry machine= 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 in traffic-mxws.py output, 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.py had its filename regex hardcoded to 2026-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".

  3. myia-ai-01 commented on Sep 18, 2026

    @myia-ai-01

    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és claude-sonnet-4-6 retraité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 en Closes #1.

  4. jsboige commented on Sep 21, 2026

    @jsboige
    OwnerAuthor

    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 :169 as the machine field. Preserved across the relay hop at relay.ts:199-208 (all non-hop-by-hop headers copied, then x-proxy-key injected and x-api-key deleted), pinned by a regression test in relay.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:92 takes -Machine as [Parameter(Mandatory)].

    The gap, measured

    grep -c 'settings.json' scripts/install-sidecar.ps1 → 1, and that one hit is Write-Host at :427: "Now repoint THIS machine's Claude Code (~/.claude/settings.json)", followed at :433 by printing the ANTHROPIC_CUSTOM_HEADERS string. 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. machine is the empty string, which is falsy in PowerShell, so CaptureUtils.psm1:64 silently falls back to the device_id fingerprint 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 opaque device:<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:

    1. on each machine, ~/.claude/settings.json actually contains X-Claudish-Machine: <name> inside env.ANTHROPIC_CUSTOM_HEADERS — no script in the repo checks this today;
    2. a recent req-*.json in the hub corpus carries machine == <name> — this one is checkable now, by grouping the machine field over a window;
    3. 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.ps1 write 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/, so find . -name 'failover.test.ts' returns 3 copies and any grep -r . triple-counts. Scope to packages/cli/src and scripts.

  5. jsboige commented on Oct 4, 2026

    @jsboige
    OwnerAuthor

    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-ProxyWithTools du watchdog, deepProbe du relais), qui n'envoient pas l'en-tête par design. L'instrument corrigé (push a7469364+) 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_id8 ni cc_entrypoint — ce ne sont pas des sessions Claude Code, mais des clients non-CC (scripts planifiés) qui nomment glm-5.3/glm-5.2 en direct contre le proxy. Même forme que la rafale du 17/09 (src=direct, ids nus) : le header ANTHROPIC_CUSTOM_HEADERS ne 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-203 hors 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).

  6. jsboige commented on Oct 10, 2026

    @jsboige
    OwnerAuthor

    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-Machine présent ? Valeur Source Mesuré
    myia-po-2023 oui myia-po-2023 %USERPROFILE%\.claude\settings.json → env.ANTHROPIC_CUSTOM_HEADERS 2026-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 bloc ANTHROPIC_CUSTOM_HEADERS m'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 un grep du 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-Machine et x-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

  7. jsboige commented on Oct 10, 2026

    @jsboige
    OwnerAuthor

    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 que x-proxy-key l'accompagne (07/10), et une lecture mono-ligne donne un faux négatif (mesuré sur ce siège). Sur le hub : l'échantillon d'enveloppes req-* (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égitime
    myia-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

    1. 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.
    2. 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.
    3. 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

  8. jsboige commented on Oct 11, 2026

    @jsboige
    OwnerAuthor

    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 relais settings re-lus en live post-bascule (21:34Z, bloc ANTHROPIC_CUSTOM_HEADERS intouché par le remplacement chirurgical de la seule ligne ANTHROPIC_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) montrant machine=myia-po-2023 sur 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions