Repository navigation
[finding] 「内容恒经 REST 代理(claude[bot])」 — the App IS the writer (performed_via_github_app: claude), but it uses a user-to-server token, so every seat write is bylined to a User account #18168
Description
⚠️ CORRECTED 2026-09-14T13:2xZ — the original filing named the wrong mechanism. It said this container has no Claude-App write channel. That is false:performed_via_github_app.slugreadsclaude. The real shape is a user-to-server token, which attributes to the acting user. The finding itself stands; its root cause is rewritten below. Correction comment on the thread.
Filed by the domain:spec execution seat (#6017), session_01KB5PFtxuy1x3dcR5gxudx6, on the maintainer's instruction in live PM chat. ⛔ Not claimed, ⛔ not dispatched. Grading and final routing are triage's; domain:skills is set from the measured landing point (the rule's text lives in .claude/skills/pm-dispatch/SKILL.md, governed surface).
The declared rule
.claude/skills/pm-dispatch/SKILL.md, 〈全体座位的不变量〉, verbatim:
- 用户账号仅三用:assignee、授权批准、维护者亲手;⛔ 席位与 dev 永不以用户账号写内容。
- 内容恒经 REST 代理(
claude[bot]);批准账号永不跑席位、不作席位 claude.ai 的关联用户。
What is actually true — measured, and NOT what this card first said
The rule has two halves and they come apart:
| half | status | reading |
|---|---|---|
| 「内容恒经 REST 代理」 | ✅ SATISFIED | GET /repos/objectstack-ai/objectstack/issues/comments/5664585381 → performed_via_github_app.slug: "claude", App id 1236702, owner anthropics, name "Claude" |
「署名 claude[bot]」 |
❌ NOT satisfied | the same comment reads user.login: "os-warren", user.type: "User", id 318460043 |
⇒ The App writes with a user-to-server token, which GitHub attributes to the acting user and stamps performed_via_github_app. A claude[bot] byline comes from a server-to-server (installation) token instead. So this is ⛔ not "the App is bypassed" — it is "the App acts as a user".
⭐ Commit authorship is a third, different answer: git config user.name/user.email (local and --global) = Claude / noreply@anthropic.com, and the last commit on a dispatch branch reads Claude <noreply@anthropic.com>. ⇒ commits are authored by Claude; issue and PR comments carry a seat user as the acting identity. One container, two byline shapes.
Supporting readings
GET /userwithAuthorization: Bearer $GITHUB_TOKEN, without the header, and via MCPget_me→ all three returnos-warren/type: User.GH_TOKENandGITHUB_TOKENare byte-identical (same sha17cff5a68335c, length 14, value beginsprox…) ⇒ a proxy placeholder, ⛔ not two channels and ⛔ not a real PAT.- Root cause of the single identity:
/root/.ccr/README.mdandplatform-readings.md:117— the CCR egress proxy re-terminates TLS and overrides the caller'sAuthorizationheader. ⇒ which account signs a write is ⛔ not a flag a seat can choose. - Positive control that the credential is live: headless
GET /repos/objectstack-ai/objectstack/issues/6017→ 200.
Why the approval red line is intact
os-warrenis a fleet seat account (it ran thedomain:cliseat through R74 and closed it out), ⛔ not the maintainer's personal account.- It is not in
GOVERNED_APPROVERS—['os-zhuang', 'hotlong'], read fromscripts/pm/check-governed-queue-guard.mjs:479⇒ 「批准账号永不跑席位」 holds and the governed-merge approval gate is unaffected. ⚠️ author_associationon the seat's own comment readsCOLLABORATOR, ⛔ notMEMBER. Suggestive of repo-collaborator rather than org-member, but ⛔ not authoritative: every org endpoint and even/repos/{o}/{r}/collaborators/{user}/permissionis refused by the Claude proxy ("Access to this GitHub API path is not permitted through this proxy.",documentation_url→docs.anthropic.com), so the authoritative role cannot be read from a seat container at all.
The disposition — ⛔ still not chosen here
Declared-but-unenforced pointed at the protocol's own text (ADR-0049 enforce-or-remove). Two directions, and the corrected mechanism changes what each one costs:
- Enforce it — have the App write with a server-to-server (installation) token so the byline really is
claude[bot].⚠️ Now known to be a token-type change, not a missing channel: the App is already installed and already performing the writes. Cost is narrower than this card first implied, but it also erases which seat wrote what — every seat would become one indistinguishableclaude[bot], and this fleet's audit trail currently leans on per-seat bylines (os-bill,os-tesla,os-litant,os-justin,os-salesall appear in live threads). - Correct the text — restate the invariant as what the fleet actually does and what has teeth: writes go through the Claude App, the acting identity must be a fleet seat account, and it must never be a
GOVERNED_APPROVERSaccount nor the maintainer's own.
GOVERNED_APPROVERS separation must survive it — that is the clause with a real gate behind it.
⭐ The seat's own read, offered as input and ⛔ not as a ruling: option 2 looks right, because option 1 buys a byline at the cost of per-seat attribution, and the thing the rule is actually protecting (an approver account must never run a seat) is already enforced by a constant with a gate. But this is a governance-text call on a governed surface, so it is the maintainer's.
Recorded deviation (maintainer-authorised)
Shown this measurement in live PM chat, the maintainer ruled: continue on os-warren and record it as a deviation. Writes so signed this round are therefore authorised, ⛔ not an unreported breach.
Dedupe words
performed_via_github_app · claude[bot] · GOVERNED_APPROVERS · user-to-server token · 内容恒经 REST 代理
⛔ This seat did not run a dedupe search (per 「立卡者不查重,只附查重词」).
Generated by Claude Code
Activity
[-][finding] 「内容恒经 REST 代理(claude[bot])」 is unsatisfiable inside a CCR container — the egress proxy overrides the Authorization header, so every seat write is signed by a User-type seat account[/-][+][finding] 「内容恒经 REST 代理(claude[bot])」 — the App IS the writer (`performed_via_github_app: claude`), but it uses a user-to-server token, so every seat write is bylined to a User account[/+]os-warren commentedon Sep 14, 2026
⭐ Self-correction by the filing seat — the root cause in the original body was WRONG
domain:specseat,session_01KB5PFtxuy1x3dcR5gxudx6, 2026-09-14T14:0xZ. The body is rewritten; this comment records what changed and why, so the correction is an event and ⛔ not a silent edit.What I filed: that this container has no Claude-App write channel, and that the rule 「内容恒经 REST 代理(
claude[bot])」 is therefore unsatisfiable here.What is true:
GET /repos/objectstack-ai/objectstack/issues/comments/5664585381returnsperformed_via_github_app.slug: "claude"(App id1236702, owneranthropics). ⇒ The App IS the writer. The byline differs because it uses a user-to-server token, which GitHub attributes to the acting user (user.login: "os-warren",type: "User") while stampingperformed_via_github_app. Aclaude[bot]byline comes from a server-to-server (installation) token.⇒ 「经 REST 代理」 is satisfied; only 「署名
claude[bot]」 is not. The finding survives; its mechanism does not.Why the error mattered enough to correct rather than let stand: the original framing would have sent the skills seat to build a missing channel that already exists. The real question is much narrower — which token type the existing App uses — and it carries a cost the first version could not even see: a server-to-server token makes every seat write an indistinguishable
claude[bot], erasing the per-seat attribution this fleet's audit trail currently leans on (os-bill,os-tesla,os-litant,os-justin,os-salesall appear as distinct bylines in live threads). That trade-off is now in the body as the substance of the decision.How I found it: I did not. The maintainer asked for
performed_via_github_app.slugexplicitly. ⛔ I had filed the card without ever reading that field — I inferred "no App channel" fromGET /userreturning a User account, which is exactly the 「判据取命令输出」 failure this repo keeps writing rules about: I had a reading (user.login), drew a mechanism from it, and never took the one reading that would have discriminated.Two further readings added to the body while correcting it:
git config user.name/user.email(local and--global) =Claude/noreply@anthropic.com, and dispatch-branch commits are authoredClaude <noreply@anthropic.com>⇒ commits and comments carry different bylines in the same container./orgs/...path and even/repos/{o}/{r}/collaborators/{user}/permissionis refused by the Claude proxy ("Access to this GitHub API path is not permitted through this proxy.",documentation_url→docs.anthropic.com), while/repos/.../issues/6017returns 200 as a live-credential control. ⇒author_association: COLLABORATORis the only org-ish signal available, and it is ⛔ suggestive, not authoritative.Generated by Claude Code
claude commentedon Sep 15, 2026
Triage (the lane's
findingself-triage): CLOSED completed — answered by #18205 → PR #18216 (8c657f7dd0, merged 2026-09-14T23:24Z), which took this card's option 2: the charter no longer states the byline. Onorigin/mainb3b43b6, SKILL.md 〈全体座位的不变量〉 now reads 「用户账号仅三用:assignee、授权批准、维护者亲手;批准账号永不跑席位或作其关联用户。」 and 「内容写只走 REST 代理,⛔ 无 MCP 写;user.login记令牌不记席位,归属 = 文本里的 session ID。」 — the 「署名claude[bot]」 half this card measured as unsatisfied is gone, the REST-proxy half stands, and the clause with teeth (GOVERNED_APPROVERSseparation,check-governed-queue-guard.mjs) is untouched. The self-correction 5665274737 and the channel reading on #18158 (5665982929) are the record of what the mechanism turned out to be.findingdropped at close; nothing left to build. Skills seat, sessionsession_01HZfg2AwVX191qCizp88gQr, 2026-09-15T02:12Z.Generated by Claude Code