Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion .claude/agents/os-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ rules that most often get missed:
2. **The issue is already claimed by the PM** (your shared GitHub identity).
Do not change assignees. If you discover the issue duplicates or conflicts
with someone else's in-flight work, stop and report `blocked`.
- **State on your PR that you did not set belongs to another actor — ask,
never "correct" it.** The shared identity makes everyone else's writes
look like yours: the PM's ready-flip and auto-merge arming, a bot's
labels, a footer the platform rewrote. #6567 is the case — a dev found
its PR's attribution footer in a form it had never typed, inferred *a
machine is editing my PR*, extended that to the **draft flag**, and
flipped the PM's ready PR back to draft. That destroys auto-merge and
merge-queue membership in one step, and `pull_request_read` exposes
neither, so the loss was silent even to the agent that caused it. The
observation was right and the inference was not: a rewritten body is
evidence about the body and nothing else. Surface the surprise in
`summary` and let the PM resolve it — reverting another actor's step is
never yours to do. The ready-flip least of all: you hand over a **draft**,
and the PM flipping it ready and arming auto-merge is the normal next
step of the process, not something acting on your PR.
3. **Scope = the issue. Nothing else.** Unrelated bugs you trip over are filed
as new **unassigned** issues (Prime Directive #10) and listed in
`out_of_scope_findings` — never fixed in this PR. Filing discipline
Expand Down Expand Up @@ -141,7 +156,9 @@ Definition of done, in order:
- Pushed with `git push -u origin claude/issue-<n>-<slug>` (retry on network
failure with backoff).
- **Draft** PR to `main`, body starting `Fixes #<n>`, explanatory prose in
Chinese per repo convention.
Chinese per repo convention. Close it with the **session-URL** attribution
footer — the bare-URL form is stripped from the stored body on every later
edit (see the sanitizer note at the end of this file).
- **`skip-changeset` label — your step, not CI's; the read-back is the proof.**
A test-only / workflow-only / `.claude/`-only PR releases nothing and writes
no changeset, so it needs the `skip-changeset` label — apply it yourself the
Expand Down Expand Up @@ -398,3 +415,22 @@ as `Assert>`) and silently truncates any prose containing a bare `<word`.
Write generics with a space after each `<` — `Assert< Equal< 1, 2 > >` is
still valid TypeScript — avoid `<`+letter in PR/issue prose, and read the
stored body back to verify whenever a snippet is load-bearing.

The same at-rest rewriting reaches the attribution footer, so write that
footer in its **session-URL** form:

```text
_Generated by [Claude Code](https://claude.ai/code)_ ← stripped on every edit
_Generated by [Claude Code](https://claude.ai/code/session_<id>)_ ← survives both paths
```

A body ending in the bare form loses the entire footer — `---` separator
included — on every `update_pull_request` edit; the session-URL form survives
both write paths (measured on PR #6556, recorded in #6567, and #6556 still
carries it today). `create_pull_request` does not strip but *rewrites* the
bare form into the session one, which is exactly how a body comes back in a
shape you never typed: that is the platform, not another agent, and it is
evidence of nothing else (rule 2). Which layer performs the rewrite is
**unknown** and deliberately not chased — the guidance holds either way.
Comments are a separate path: the bare form survives there untouched, so a
claim or finding comment needs no special handling.
36 changes: 36 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,42 @@ post-mortem in #4588), and misreading shared-identity state is also how a
maintainer's manual ready-flip got reverted by an agent that assumed its own
write had failed.

**State on your PR that you did not set belongs to another actor — ask, never
"correct" it.** That is the same misread one step on, and the more expensive
half. Under one shared identity every other participant's write arrives
unsigned: the PM flipping your draft to ready and arming auto-merge, a bot
re-labelling, the platform rewriting your body. #6567 is the worked example —
a dev read its own PR body back, found the trailing `Generated by [Claude
Code]` footer in a form it had never typed, correctly concluded *something is
rewriting my PR*, then carried that conclusion to the **draft flag** and
flipped the PM's ready PR back to draft. That drops auto-merge and queue
membership at once (§7's third re-arm situation), and `pull_request_read`
reports neither, so the agent could not see what it had destroyed. The
observation was right; the second inference did not follow from it — body
rewriting is a known platform behaviour and is **evidence of nothing else**.
So when state you did not write changes under you: read the timeline event's
actor, or ask the PM. Undo it only once you know who set it and why.

**Write the attribution footer in its session-URL form** — that is the half of
the above you can act on directly. Measured on PR #6556 and recorded in #6567:

```text
_Generated by [Claude Code](https://claude.ai/code)_ ← stripped on edit
_Generated by [Claude Code](https://claude.ai/code/session_<id>)_ ← survives
```

A body ending in the bare form loses the **whole** footer, `---` separator
included, on every `update_pull_request` edit (reproduced twice, including with
a blank line before the separator), while the session-URL form survives both
write paths — #6556 still carries it. `create_pull_request` does not
strip; it *rewrites* the bare form into the session form, which is precisely
how a body comes back in a shape nobody typed. **Which layer does this is
unknown** — platform sanitizer, MCP tool layer, or a workflow's body
post-processing — and the guidance does not depend on the answer, so nobody
should spend a session establishing it. Comments are a different path and are
unaffected: the bare form survives untouched in issue and PR comments,
including the two on #6567 itself.

The claim is also what makes the *finding* rule (Prime Directive #10) safe to
follow. Once out-of-scope discoveries become issues, the issue list is a real
queue other agents read, and a claim is the only thing separating "someone is on
Expand Down
Loading