Late replies must match the current identity before they write
The pattern to watch for
A learner is five seconds into an audio lesson. In the background, their account finishes a quiz sync that started a while ago. The sync response arrives and reroutes the page, and the player does what a reroute tells it to: it pauses and seeks to the saved position. The saved position is zero, so the lesson starts again from the beginning.
A reviewer agent reproduced that from the exact source at a pinned commit. The fixture showed an active position of five seconds going back to zero. The cause was ordinary: one course had been left off a sync exemption list, so a late response was allowed to rewrite what the learner was doing now.
That is the pattern. Something late (a reply, an event, a release, an interrupt whose outcome nobody knows) changes current state on behalf of something that is no longer current. Over about a week of running agent fleets across several products, it showed up at least four times, in four different forms. None appeared in normal use or a happy-path test. Every one came with a reproduction.
Four products, one bug
The products had little in common:
- A crash-course site with an audio player. Lessons play one after another, with routes, saved positions and a segmented audio stream.
- A desktop app that lets an agent borrow my machine under a lease, with a Stop control that has to stay usable.
- A parental-controls platform where drafts and commits change a daily screen-time allowance, measured in minutes.
- My agent-harness console, which supervises reviewer agents and queues work for them.
Each project had independent reviewer agents whose rule was to reproduce a defect before reporting it. That rule matters for everything below.
The audio player: rewinds and an early end
The first course's player rewound at lesson boundaries. Failure-first tests reproduced rewinds at 19.98 s, at 20.1 s before the next lesson's route update arrived, and after an ordinary pause near the boundary. The player had been treating "near the end" and "paused near the end" as "finished". The fix: replay now requires actual ended media or the player's trusted finished state, and buffering and pause recovery keep the current position. That landed with 54/54 focused tests and went back for independent re-review.
On a sibling course, a reviewer blocked the scaffold for two defects. One was the delayed account sync from the opening: the exemption that kept active lessons from being rerouted after a quiz sync covered two courses but not this one. The other was an end event that arrived before the queued route update and could replay the final lesson. The code worked out which lesson had ended from page state, and page state can lag the stream's own lesson id. After a natural move from lesson 1 to lesson 2, followed immediately by the end event, the page still believed it was on lesson 1 and set a start flag. The next render called play, which restarts ended audio.
The reviewer's instruction was the thesis of this note in one line: use the validated active stream lesson for the end decision, and test end-before-route ordering. The owner reported both defects repaired, with failing-before controls and seven passing tests. That repaired commit was outside the reviewer's packet, so it still needed its own review. I am not calling it verified here.
The stream side had a related issue. A review blocker was raised about timestamp resets in the playlist. After the repair, the playlist marks both inherited and new timestamp resets, and 127 tests, a full decode of the playlist, transport hashes and waveform checks passed. Same idea, different layer: if the stream's own sense of time changes, the player has to be told, or it applies old offsets to new segments.
The desktop lease: a cleanup step that stopped the wrong lease
This took three review rounds. Earlier repairs had added guards that correctly rejected an obsolete pause result. But the surrounding cleanup block then issued a fresh stop call, and that call picked up whatever attachment was current at that moment, which was the replacement.
The reviewer reproduced it with the real classes and fake boundaries for the host and the desktop shell. In outline: an automatic safety Stop began on lease A, the test held that request, a replacement lease B attached, and A's pending request was then rejected. The stale result was correctly ignored, but the cleanup step's fresh stop landed on B. A safety action that belonged to A had stopped B.
This is the version that bothers me most. The guard existed and worked on the call it guarded. The identity check covered one step, but the sequence had several, and the last step looked up "current" again instead of carrying the identity it started with. The existing composition test rejected A's role request after B attached, so it never covered an automatic Stop already in progress on A. The reviewer noted this was the same material finding after two repair rounds, which meets my threshold for a clean-worktree rescue. These notes do not record how the rescue went, so I am leaving the outcome open.
The allowance: a conflict response that still wrote
A new lease on a commit path was meant to stop stale drafts from overwriting newer ones. A reviewer reproduced, through real HTTP with a controlled clock and a paused external lookup, what happens after the lease expires:
- A newer draft saved a 30-minute allowance.
- The old commit returned 409 conflict, and then applied its 60-minute allowance anyway.
The response told the caller it had lost, and the write happened regardless. A second flow had the same reproduced failure. The required fix was a deterministic expiry-barrier regression for both flows, and preventing an expired commit from making any further writes.
A lease that only gates the start of an operation does nothing about the writes it makes later. Each write has to show the lease is still its own.
The console: unknown quietly became stopped
In the harness console, when a reviewer's interruption was refused or could not be confirmed, the lifecycle code correctly marked the outcome as unknown and released the waiter. The awakened code then unconditionally marked the child as not stoppable, and the interface disabled Stop for a reviewer that might still be running.
The red-team reproduced it with a synthetic transport that acknowledges review start and explicitly refuses interrupts. The failing test output showed status unknown with Stop disabled. The required fix: keep the unknown state and its reason visible, and keep Stop and reconcile controls until a terminal outcome is confirmed.
This is the stale-event problem turned around. The system reacted to an event that had not happened, and treated "I don't know" as "it's over".
The same console later passed a native queue campaign. In a real Codex run (47.86 s), one task was queued during a review and another cancelled, and the native trace captured both required refusals. One was a refusal of stale completion authority: a completion from an earlier generation could no longer close the current one. Both the Codex and Claude runs kept the same task, generation and native identity, moved from revision 1 to revision 2, and cleared the old gates.
Why it happens
Side by side, the cases share one shape:
| Late thing | What it changed | What it should have checked |
|---|---|---|
| Account sync response | Paused and rewound the active lesson | Is this lesson still the one playing? |
| End event before route update | Replayed the final lesson | Which lesson does the stream say ended? |
| Cleanup after a stale pause | Stopped the replacement lease | Is the attachment I started with still current? |
| Commit after lease expiry | Wrote 60 minutes over 30 | Do I still hold the lease for this write? |
| Refused interrupt | Disabled Stop | Is there a terminal outcome yet? |
Async code is usually written as if the world waits for it. A handler, a cleanup block, a retry or a timer runs later, and later is exactly when current state may belong to someone else. The common move in every case was to re-read "current" at write time, or to read it from a source that lags (page state rather than the stream), instead of carrying the identity the operation started with.
The same problem showed up in the small sample apps used early on to test the harness. Reviewers kept finding stale async UI state: an earlier response clearing a newer draft, or confirmation controls staying clickable after submit. There is a softer version in process too. An approval for one piece of work does not carry over to a new interface on the same project, because an approval is also an event with an identity. I go further into that in making human approval survive changes in payload and context.
How to detect it
None of these bugs surfaced by using the product normally. Each was reproduced by controlling the order of events:
- a held pause request while a second lease attached,
- a paused external lookup with a controlled clock,
- a synthetic transport that refuses interrupts,
- an end event placed just before a route update.
If you cannot control the clock and the boundaries, you cannot reproduce these, and you cannot show a fix is real. When I review async code, I look for any continuation that reads "current" rather than receiving it, and any status that maps "unconfirmed" onto a terminal value. The second is the theme of treating unknown as an outcome.
How to prevent it
- Carry identity, don't look it up again. Every continuation captures the stream, lease, turn or generation id it started with, and checks it before every write, not just the first.
- Make expiry a barrier on writes. An expired lease blocks later writes, and the response code and the side effects have to agree.
- Decide what ended from the stream, not the page. The media element and the stream's lesson id are the source of truth for boundaries.
- Keep unknown visible. When an interrupt or cancel cannot be confirmed, keep Stop and reconcile available until a terminal state arrives.
- Put these cases in the brief, not the review. After the sample-app findings, I added delayed-response, retry and idempotency cases to the owner preflight. If the reviewer is the first to think about a late reply, it is already one round late.
A note on status. The reviewers' reproductions are solid. These notes do not show the merged fixes for the lease crossover, the allowance expiry or the console Stop control, and the sibling course's repair was still waiting for review. The pattern is established. Most of the repairs, as recorded here, are not yet verified.
Checklist
- [ ] Every async continuation (handler, cleanup block, retry, timer) carries the id it started with.
- [ ] That id is checked before each write, not only the first.
- [ ] An expired lease cannot write, and a conflict response never comes with a side effect.
- [ ] Boundary decisions (what ended, what is playing) come from the stream or media element, not page state.
- [ ] Unconfirmed interrupts stay "unknown" with a visible reason, and Stop stays enabled.
- [ ] Tests control the clock and hold boundary calls to force each ordering.
- [ ] Required orderings: delayed response, end-before-route, stale release after replacement, commit after expiry, refused interrupt.
- [ ] Each ordering test fails on the old code before it passes on the new.
- [ ] Repaired commits outside a reviewer's packet get their own review before anyone calls them verified.
Go deeper: async state, leases and unknown outcomes are worked through in the Transition2 course.