Skip to content

fix(codex-activity): one-batch start+clear on a Pending turn records the completion (kata pkvz) - #750

Merged
danshapiro merged 11 commits into
mainfrom
the-usual/pkvz-deflake
Sep 7, 2026
Merged

fix(codex-activity): one-batch start+clear on a Pending turn records the completion (kata pkvz)#750
danshapiro merged 11 commits into
mainfrom
the-usual/pkvz-deflake

Conversation

@danshapiro

Copy link
Copy Markdown
Owner

Root cause

Kata pkvz: fresh_pane_locator_identity_reaches_activity_and_turn_complete flaked under whole-workspace cargo test load. The codex activity tracker's reconcile_rollout promotion guard computed effective_clear = max_ts(observed_clear, last_cleared_at). When the rollout's task_started+task_complete were read in one batch (the load-induced hub drain ordering), the same-batch clear shadowed the start promotion — accepted_start_at stayed None, the Pending clear branch re-armed via has_queued_submit's unwrap_or(true), and terminal.turn.complete never fired.

Fix

For a LIVE (Pending) turn whose start belongs to the pending submit (started_at >= pending_submit_at, matching the TS reference codex-activity-tracker.ts:446), bypass the same-batch clear in the promotion guard — the start-then-clear is the pending submit's complete turn cycle, not a stale echo. Historical rollouts (start before the pending submit) and Idle rollouts keep the same-batch clear, preserving reconcile_ignores_an_already_resolved_rollout.

crates/freshell-activity/src/codex.rs:389

Tests

  • 3 new unit tests in freshell-activity/src/codex.rs (deterministic RED→GREEN):
    • reconcile_one_batch_start_and_clear_on_a_pending_turn_with_newer_start_completes — the suppression (RED without fix, GREEN with fix)
    • reconcile_one_batch_start_and_clear_on_a_pending_turn_with_older_start_rearms — re-arm parity (no over-ring)
    • reconcile_one_batch_historical_start_before_pending_submit_records_nothing — regression guard for historical rollouts
  • 1 new integration test in freshell-ws/tests/codex_locator_activity.rs (non-regression guard): fresh_pane_locator_one_batch_drain_records_turn_complete — proves the one-batch drain path works end-to-end through the real server, socket, PTY, inotify watcher, and activity hub
  • All 196 existing tests stay green (188 activity unit + 2 locator + 4 fork_rebind + 2 rest_locator)
  • cargo fmt --check clean, cargo clippy clean

Notes

  • PR test: deflake CI rust-test timing (pkvz + dead_session WARN) #744 already bumped the wait_for_frame budget 30s→120s (symptom suppression); this PR fixes the root cause.
  • The Node tracker (server/coding-cli/codex-activity-tracker.ts) has the same suppression — recorded as an out-of-scope finding for a separate PR.
  • Plan review went 3 rounds (all findings fixed); delta review went 5 rounds (all fixable findings fixed; remaining blocker: the integration test can't deterministically reproduce the suppression from the WS client — the unit tests are the deterministic proof, and all reviewers agree the production fix is correct).

…id cargo commands, whole-crate load check, no escape clause
…eplaces invalid load check, fix stale base metadata
…the completion (kata pkvz)

reconcile_rollout's promotion guard computed effective_clear from the same-batch
observed_clear, so a LIVE (Pending) turn whose rollout was drained in one batch
(task_started+task_complete together, the load-induced hub drain ordering) had
its promotion shadowed by its own clear: accepted_start_at stayed None, the
Pending clear branch re-armed via has_queued_submit's unwrap_or(true), and
terminal.turn.complete never fired (kata pkvz). Bypass the same-batch clear for
a Pending turn whose start belongs to the pending submit (started_at >=
pending_submit_at, matching the TS reference codex-activity-tracker.ts:446), so
the one-batch live turn promotes-then-clears and records exactly one completion
-- matching the already-green separate-batch path. Historical rollouts whose
start predates the pending submit keep the same-batch clear in the guard (no
false completion), and Idle rollouts keep it unconditionally, preserving
reconcile_ignores_an_already_resolved_rollout.
…kata pkvz)

Forces the load-induced one-batch path deterministically: writes session_meta +
task_started + task_complete ALL AT ONCE before the locator resolves, so
CodexAttach's initial drain reads all three lines in one reconcile_rollout call.
Uses far-future timestamps so task_started > queued_submit_at (matching the
original flake's timeline where task_started is appended after both Enters).
Fails before the Task 1 fix (same-batch clear shadows the promotion, 120s
timeout); passes after (5s). No load dependence — the root-cause end-to-end proof.
…delta review r1)

Adding a second test to the binary invalidated the single-sequential-test
assumption: both tests mutate CODEX_HOME/CODEX_ARGV_CAPTURE_PATH and share the
PID-only fake-script path. Add ENV_LOCK (the repo's convention, e.g.
codex_fork_rebind.rs) to serialize them, preventing env races and ETXTBSY under
concurrent libtest execution. Also trims the production comment to a short note
per the plan's constraint.
…ta review r2)

Replace the blind 3s sleep with a wait_for_frame for codex.activity.updated
phase=pending (proves note_possible_submit completed the re-snapshot AND
note_input set Pending), then a 2.2s sleep for the 2s window resolution. This
eliminates the load-sensitive race where the server hadn't processed the first
Enter before the rollout was written (which would permanently exclude it from
the re-snapshot).
…tep + R3 evidence (delta review r3)

- rustfmt: format the production conditional, the historical-start fixture, and the pending-frame predicate.
- Test: increase the post-pending sleep to 3s (matching the existing test's proven 850ms margin over the 2.15s worst-case window resolution) and document why it's sufficient in isolation.
- Plan: the RED step now uses git stash to temporarily revert the fix (verified during execution: 125s timeout without the fix).
- Plan: R3 clarifies that the deterministic one-batch test is a STRONGER proof than a probabilistic loaded run.
The integration test can't deterministically reproduce the one-batch suppression
(the queued-submit race is not deterministically controllable from the WS
client). Reframe it as a non-regression guard: writes session_meta +
task_started + task_complete within the first 2s window (after the pending frame
proves the re-snapshot completed), and the 150ms sweep finds it. The
deterministic RED/GREEN evidence is in the three unit tests (which set
queued_submit_at directly). Removes the second Enter, the 3s sleep, and the
unreachable RED step from the plan. 7.5s vs 10.5s.
@danshapiro
danshapiro merged commit 65ae62f into main Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant