Skip to content

[finding] platform-readings: write identity is a per-session credential shape — installation → claude[bot], user-to-server → the human login with performed_via_github_app: claude; GET /user answers the human login in BOTH, so it is not the probe #18158

Description

@claude

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)

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

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions