Split out of the review of #35, where it was raised three times. The mechanism is real; it is documented there as a rollout requirement rather than solved in code, and this issue is the place to solve it properly.
The mechanism
Round.CoBots is additive on schema v3, so an old binary reads the state fine. But Go's encoding/json drops unknown fields, so an old binary that writes state marshals the round without cobots — erasing every Bugbot/Macroscope command id and CAS claim the new binary recorded.
Blast radius
Narrower than it looks, and worth stating precisely so this is not over-fixed:
- CodeRabbit's no-double-fire invariant is untouched. It rests on
FireSlot plus CommandID, both legacy fields old binaries preserve. Codex is dual-written into CodexCommandID/CodexCommandedAt/CodexClaimedAt for exactly this reason.
- What an erasure can cost is a duplicate co-reviewer trigger comment, which spends no CodeRabbit quota.
observe narrows it further: the next pass sees the already-posted bugbot run on the PR and suppresses a repost. The exposed window is between claim and post — seconds — not the claim's lifetime.
Why not just bump the schema
Considered and rejected: an old binary meeting a version it does not know auto-reinitialises the shared state, losing every round in the fleet rather than one map — and it would do so during exactly the rolling deployment this is about. That trades a bounded loss for an unbounded one.
Current mitigation
Documented in #35 under "Rollout requirement": upgrade the whole fleet before enabling Bugbot or Macroscope.
Possible approaches
- Dual-write the co-bot map into a field old binaries already preserve (as Codex is), at the cost of a fixed set of bots.
- A version-tolerant round representation that round-trips unknown fields (decode into
json.RawMessage and re-emit), which fixes this class of problem generally rather than for CoBots alone.
- A staged rollout marker that makes new-binary features conditional on the whole fleet having upgraded.
Split out of the review of #35, where it was raised three times. The mechanism is real; it is documented there as a rollout requirement rather than solved in code, and this issue is the place to solve it properly.
The mechanism
Round.CoBotsis additive on schema v3, so an old binary reads the state fine. But Go'sencoding/jsondrops unknown fields, so an old binary that writes state marshals the round withoutcobots— erasing every Bugbot/Macroscope command id and CAS claim the new binary recorded.Blast radius
Narrower than it looks, and worth stating precisely so this is not over-fixed:
FireSlotplusCommandID, both legacy fields old binaries preserve. Codex is dual-written intoCodexCommandID/CodexCommandedAt/CodexClaimedAtfor exactly this reason.observenarrows it further: the next pass sees the already-postedbugbot runon the PR and suppresses a repost. The exposed window is between claim and post — seconds — not the claim's lifetime.Why not just bump the schema
Considered and rejected: an old binary meeting a version it does not know auto-reinitialises the shared state, losing every round in the fleet rather than one map — and it would do so during exactly the rolling deployment this is about. That trades a bounded loss for an unbounded one.
Current mitigation
Documented in #35 under "Rollout requirement": upgrade the whole fleet before enabling Bugbot or Macroscope.
Possible approaches
json.RawMessageand re-emit), which fixes this class of problem generally rather than forCoBotsalone.