Finding — write identity is a per-session credential shape, and GET /user does not reveal it
Filed by the domain:skills execution PM seat (#7623), session session_01DAcomhvR9kKizeYgg89Vo8, 2026-09-14T08:02Z. Bare finding; grading is the lane's own first-touch (skills-lane self-triage exception). Keywords: platform-readings, rest-channel, write identity, claude[bot], installation token, user-to-server, performed_via_github_app, suspension exposure.
What was measured (two live seats, same GitHub App, two authorship shapes)
This seat (session_01DAcomhvR9kKizeYgg89Vo8), container REST channel through the session proxy, read at 2026-09-14T07:57Z:
| probe |
answer |
GET /user |
200 — login os-project-manager, type User |
X-Ratelimit-Limit on /repos/objectstack-ai/objectstack |
15000; /rate_limit core 15000, graphql 10000 |
X-Oauth-Scopes |
empty |
GET /installation/repositories |
403 |
GET /user/installations |
403 |
| authorship of the seat's own writes |
claude[bot], type Bot — 89 of the last 100 issue comments in this repo (sort=updated) |
The domain:cli seat (session_01TbSMtGzMrtPwh925wDEZd5, closed out 04:49Z on #6024) reported that GET /user answers os-warren, type User, under no header, its header, and a deliberately invalid header alike, and read that as "no channel writes as claude[bot]". Its last three writes, read back at 07:59Z:
| comment |
user.login |
user.type |
performed_via_github_app |
5659176913 (#17978 landing record) |
os-warren |
User |
slug claude, id 1236702 |
5659181387 (#6024 close-out addendum) |
os-warren |
User |
slug claude, id 1236702 |
5659189335 (#6024 handover correction) |
os-warren |
User |
slug claude, id 1236702 |
Same App (claude, id 1236702, owner anthropics) on both seats. One session carries an installation credential (writes author as claude[bot]), the other a user-to-server credential (writes author as the human login, with performed_via_github_app set). GET /user answers the session's human login in both shapes, so it is not the probe that separates them; the rate-limit ceiling does not separate them either (a user-to-server token on an Enterprise Cloud installation is also 15,000/h per GitHub's rate-limit docs).
Why it matters
Register lines that now over-claim
references/platform-readings.md :129 — 「容器 curl 的 REST 通道 = App installation token,core 15,000/时」 holds for this session class, not for every container: the cli seat's container writes user-to-server through the same proxy.
references/platform-readings.md :116 — 「无 header 的 REST 读回 200 带会话身份」: the session identity GET /user reports is not the write identity.
references/rest-channel.md :13 — 「凭据 GET /user ≠ 被拒 user ID 才换」 compares on GET /user; the comparison that matters for authorship is the read-back of an own write.
Proposed readings (one line each; the dev decides the exact wording)
- Write identity is fixed at session provisioning and is not readable from
GET /user (it answers the human login under both shapes). The probe is the read-back of the session's own last write: user.type: Bot + login claude[bot] = installation credential; user.type: User + performed_via_github_app.slug: claude = user-to-server credential.
- A seat whose read-back shows
User must not write content (comments, bodies, reviews, ready/auto-merge) until re-provisioned; reads are unaffected. Re-provisioning is the maintainer's action on the claude.ai side.
- Qualify :129 accordingly rather than delete it: it stays true for installation-credential sessions.
Probe, one line, with the id of a comment the session has just posted (no secret values in it; the variable name is the container's own):
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/objectstack-ai/objectstack/issues/comments/COMMENT_ID | python3 -c 'import json,sys; c=json.load(sys.stdin); print(c["user"]["login"], c["user"]["type"], (c.get("performed_via_github_app") or {}).get("slug"))'
Duplicate check
The repo-scoped issue listing (labels=finding&state=open, 32 cards) and the last 60 updated open issues carry no card on write-identity shape; the nearest neighbours are #18147 (what a suspension erases) and #18130 (CCR routes on the proxy). GET /search/issues is not available on this channel (403 「sessions are bound to their configured repositories」), so the check is the listing, not a search.
Generated by Claude Code
Finding — write identity is a per-session credential shape, and
GET /userdoes not reveal itFiled by the
domain:skillsexecution PM seat (#7623), sessionsession_01DAcomhvR9kKizeYgg89Vo8, 2026-09-14T08:02Z. Barefinding; grading is the lane's own first-touch (skills-lane self-triage exception). Keywords:platform-readings,rest-channel, write identity,claude[bot], installation token, user-to-server,performed_via_github_app, suspension exposure.What was measured (two live seats, same GitHub App, two authorship shapes)
This seat (
session_01DAcomhvR9kKizeYgg89Vo8), container REST channel through the session proxy, read at 2026-09-14T07:57Z:GET /user200— loginos-project-manager, typeUserX-Ratelimit-Limiton/repos/objectstack-ai/objectstack15000;/rate_limitcore 15000, graphql 10000X-Oauth-ScopesGET /installation/repositories403GET /user/installations403claude[bot], typeBot— 89 of the last 100 issue comments in this repo (sort=updated)The
domain:cliseat (session_01TbSMtGzMrtPwh925wDEZd5, closed out 04:49Z on #6024) reported thatGET /useranswersos-warren, typeUser, under no header, its header, and a deliberately invalid header alike, and read that as "no channel writes asclaude[bot]". Its last three writes, read back at 07:59Z:user.loginuser.typeperformed_via_github_app5659176913(#17978 landing record)os-warrenUserclaude, id12367025659181387(#6024 close-out addendum)os-warrenUserclaude, id12367025659189335(#6024 handover correction)os-warrenUserclaude, id1236702Same App (
claude, id 1236702, owneranthropics) on both seats. One session carries an installation credential (writes author asclaude[bot]), the other a user-to-server credential (writes author as the human login, withperformed_via_github_appset).GET /useranswers the session's human login in both shapes, so it is not the probe that separates them; the rate-limit ceiling does not separate them either (a user-to-server token on an Enterprise Cloud installation is also 15,000/h per GitHub's rate-limit docs).Why it matters
labeledactor is gone) — and the one variable shared by the two suspended triage accounts is the hourly periodic-burst shape, not volume #18147: content and events go with the account). A seat running on it is putting its human's account at risk with every comment, and the seat cannot tell fromGET /user.Authorizationheader (a bogus header still answers with the session's credential). Re-provisioning is a maintainer action on the claude.ai side, not a seat action.Register lines that now over-claim
references/platform-readings.md:129 — 「容器 curl 的 REST 通道 = App installation token,core 15,000/时」 holds for this session class, not for every container: the cli seat's container writes user-to-server through the same proxy.references/platform-readings.md:116 — 「无 header 的 REST 读回 200 带会话身份」: the session identityGET /userreports is not the write identity.references/rest-channel.md:13 — 「凭据GET /user≠ 被拒 user ID 才换」 compares onGET /user; the comparison that matters for authorship is the read-back of an own write.Proposed readings (one line each; the dev decides the exact wording)
GET /user(it answers the human login under both shapes). The probe is the read-back of the session's own last write:user.type: Bot+ loginclaude[bot]= installation credential;user.type: User+performed_via_github_app.slug: claude= user-to-server credential.Usermust not write content (comments, bodies, reviews, ready/auto-merge) until re-provisioned; reads are unaffected. Re-provisioning is the maintainer's action on the claude.ai side.Probe, one line, with the id of a comment the session has just posted (no secret values in it; the variable name is the container's own):
Duplicate check
The repo-scoped issue listing (
labels=finding&state=open, 32 cards) and the last 60 updated open issues carry no card on write-identity shape; the nearest neighbours are #18147 (what a suspension erases) and #18130 (CCR routes on the proxy).GET /search/issuesis not available on this channel (403 「sessions are bound to their configured repositories」), so the check is the listing, not a search.Generated by Claude Code