Skip to content

finding: random changeset filenames collide silently across parallel agents — a round overwrote a sibling PR's minor changeset and every gate stayed green #17712

Description

@os-bill

Filed by the domain:spec execution seat, 2026-09-11T16:5xZ, from a NEAR MISS that a round self-caught in #17400 and reported rather than quietly repairing. ⛔ No domain:* and no priority from me.

What happened, measured

A round wrote its changeset with a changesets-style random word-pair filename.changeset/plain-donkeys-repeat.md. That file already existed on origin/main: it is a sibling PR's minor changeset. The round overwrote it and committed the overwrite.

It was caught by one thing only: git status printed M where an A was expected.

Repaired completely and provably — the sibling file restored from origin/main and shown byte-identical by git hash-object (ee8dfe6a85… == ee8dfe6a85…), the round's own changeset renamed to an issue-scoped name, and the sibling path absent from the final diff vs origin/main (0 matches). ⇒ ⛔ Nothing was actually lost. This card is about the mechanism, not the incident.

⭐ Why this is worth a card

Nothing mechanical would have stopped it. Walk the failure that almost shipped:

  1. A changeset filename is content-free — plain-donkeys-repeat.md says nothing about what it releases, so a collision looks like nothing.
  2. Overwriting it produces a valid changeset file. check:empty-changeset and check-changeset-no-major both still pass — the file parses, it just now says something else.
  3. The sibling PR's release note is silently replaced by this PR's. The sibling stays green; its own CI never re-runs on this change.
  4. The loss surfaces at release time, in the generated CHANGELOG, by which point the authoring PR is merged and the evidence is gone.

⇒ A minor release note for a shipped package could be deleted by an unrelated PR, with every gate green on both sides. That is a release-integrity hazard, not a working-style preference.

⚠️ And the exposure is structural here: this repo runs many agents in parallel, each creating changesets, all drawing from the same small random-word space. The collision rate is not hypothetical — it happened today on the first round that used a random name.

The cheap mechanical fix (⛔ a direction, not a prescription)

Every other round today named its changeset issue-scoped.changeset/17187-…md, .changeset/16875-…md — and those cannot collide by construction. Options a taker might weigh:

  • A — a gate refusing a changeset filename that is not issue-scoped (or refusing a diff whose changeset entry is M rather than A). ⭐ The M-not-A shape is exactly what caught this one, so it is already known to be detectable.
  • B — an os-dev rule in .claude/agents/os-dev.md mandating the issue-scoped name. Cheaper, but prose, so it binds only rounds that read it.
  • C — nothing: treat it as working discipline.

⚠️ A and B are not equivalent, and the difference is the whole point of this card: B is the same class of protection that just failed. The round that collided was following its dispatch faithfully — the dispatch simply had not told it to scope the name. Another prose rule protects only the rounds that happen to be told.

⛔ I am not choosing: a new gate is gate strength, which is the maintainer's floor.

An adjacent hazard from the same round, recorded not filed

On this container's shallow clone, git log -- PATH attributes every file to the graft-boundary commit dea214f818, so path provenance is unusable without --unshallow. That round noticed and declined to rely on it. ⛔ Not a repo defect — a property of the checkout — but it is the kind of thing that turns into a false "this file has not changed since" reading.

Duplicate check — completed to the limit this session allows

⛔ REST /search/* is refused for this session and MCP search_issues is rate-limited. Completed: enumeration of 569 open issues grepped by TITLErandom 0, overwrite 0, plain-donkeys 0; changeset 6 and collision 3, each a different subject on inspection (#17296 an ADR-0049 question, #17281 a retirement's trailing edges, #17229 a false gate line, #17026 stale claims inside a queued changeset, #14290 a dispatch-gates traversal gap). Lit control spec = 75. ⚠️ Titles only; bodies were not searched. Close as duplicate if one surfaces.

Related: #17400 / PR #17711 (where it happened and was repaired) · #17026 (a different way a changeset's content goes wrong after it is written)


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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions