Skip to content

test(pending): start a first message's same-kind replacement before the first agent ends - #2222

Merged
devswha merged 1 commit into
mainfrom
fix/main-integration-failures
Oct 28, 2026
Merged

devswha merged 1 commit into
mainfrom
fix/main-integration-failures

Conversation

@devswha

@devswha devswha commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Found by the main-integration lane: Integration failed on main run 40644839354 (30cd84f) and on #2176's run 40643516119.

Cause (test flake, not a product regression)

a create request's first message > is bound to the agent process it was queued under: a replacement of the same kind ... started SESSION2 from the shell (recorder SESSION1; exec recorder SESSION2). Between SIGKILL of SESSION1 and the exec, bash alone is in front. A pane event in that gap makes launchGone (shell back in front, server/core/pending.ts) hold the message as first_message_launch_ended. That is the documented behaviour from #1969: the launched agent did end, and no agent was in front. The test expected pending_target_changed, so it failed whenever a read landed in the gap.

Reproduced: keeping the shell busy for a moment before the exec (a builtin loop, so the shell itself stays in front) fails the old test 3 of 3, with exactly CI's Received: "first_message_launch_ended". A sleep 1 there does not reproduce it, because sleep is another process and reads as a changed target.

Change

Proof

  • Mutation: with samePendingIdentity ignoring process pid and birth, the new test fails 2 of 2 (the message is typed into SESSION2).
  • Stress: the first-message block plus the two replacement tests (21 tests) passed 10 of 10 on CI's Node 22 and herdr 0.9.3, through dori heavy on a loaded host.
  • Local checks: bun run check fast ok.

Test-only: no app code changes. Splits from the same lane: #2223 and #2224.

Reviews

  • omo-native-code-reviewer, round 1 on the combined change: BLOCK with two HIGHs, both fixed. One was a loop shape that scripts/conventions.test.ts counts as a fixed sleep; the other was the OpenCode helper not waiting for its own Ctrl-L. Round 2 on these heads: APPROVE, no findings above LOW.
  • Test-only, so no Codex review (notes/pr-policy.md: the test-only gate is omo alone).

…he first agent ends

The shell was alone in front between SESSION1's kill and SESSION2's exec, and a read there rightly holds the
message as first_message_launch_ended (#1969), so the test expecting pending_target_changed failed now and then
(main run 40644839354, #2176's run 40643516119). The launcher fixture now starts SESSION2 on SIGUSR1 and ends
SESSION1 once it runs.
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between b257be8 and ffa7612.

Files selected for processing: 2
  • changelog.d/integration-flake-first-message-replacement.md
  • server/pending-input.contract.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

📝 Summary

Summary by CodeRabbit

  • Tests
    • Updated the process-replacement integration test to verify that a replacement agent starts before the original agent exits, reducing intermittent failures caused by timing gaps.
  • Documentation
    • Clarified the integration-test timing behavior in the changelog.

The integration test now signals a launcher to start SESSION2 before killing SESSION1. It waits for SESSION1 to leave the foreground process list, then checks for a foreground Claude replacement. The changelog describes the prior timing gap and intermittent test failure.

Changes: Replacement test flow

Layer / File(s) Summary
Launcher-driven replacement test
server/pending-input.contract.test.ts, changelog.d/integration-flake-first-message-replacement.md
The launcher starts SESSION2 on SIGUSR1 and kills SESSION1 after SESSION2 spawns. The test signals the launcher, waits for SESSION1 to disappear, and checks for a foreground Claude replacement. The changelog describes the prior timing gap and intermittent failure.

Priority: Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Other · Severity of issue fixed: Low

Possibly related PRs

  • devswha/ovis#1969: This PR updates the same first-message contract fixture to test replacement while the launcher remains in front.

Suggested reviewers: odfalik

Merge Risk: Minimal · up to ffa76

No concrete merge-blocking risk was established for this fixture-only change.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title names the main change: a test change that starts a first message's same-kind replacement before the first agent ends. The PR objectives confirm the fixture now starts SESSION2 on SIGUSR1 and…
Description check Passed The description follows the template. The Change section describes the problem (a test flake caused by the shell being in front between SESSION1 and SESSION2) and the resulting behavior (the launcher …
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: a test change that starts a first message's same-kind replacement before the first agent ends. The PR objectives confirm the fixture now starts SESSION2 on SIGUSR1 and ends SESSION1 after it spawns, which fits the title. The title is specific and single-sentence, and it uses the conventional test(scope) prefix.

Full details: Description check

Explanation

The description follows the template. The Change section describes the problem (a test flake caused by the shell being in front between SESSION1 and SESSION2) and the resulting behavior (the launcher fixture now starts SESSION2 before SESSION1 ends). The Validation section lists the mutation test, the stress run, and the local check results, along with the review outcome. The header references CONTRIBUTING.md for checks, but the description does not state whether contract-test coverage applies. This is a test-only change with no HTTP/WS changes, so that omission is minor. The description also includes a changelog entry in changelog.d/, which the template comment asks for.

Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 1 files. (1 skipped: 1 unsupported.)

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devswha added a commit that referenced this pull request Oct 28, 2026
…e, after an unobserved switch-back (#2224)

Found by the main-integration lane: Integration failed on run
40646297534 (ci/self-hosted-runner-cache, main 70e19f4).

## Cause (test flake)

The Copilot stand-in writes a `:switch` as two log lines,
`Unregistering` and then `Registering`. A conversation read between them
rightly fails closed: scrollback, no turns, and another `history_id`.
The test's wait for the unobserved switch-back stopped at any
`history_id` change, so it could stop on that read and then fail with
`turns[0]...text` `undefined`.

**Reproduced:** with 400 ms between the two writes, the old test fails 3
of 3 with CI's `Expected: "...a03" Received: undefined`, and the fixed
one passes 3 of 3.

## Change

The loop waits for the Copilot transcript itself: source
`copilot-transcript`, a new `history_id`, and the resumed session's
first turn. It still throws after 5 s if the binding never advances.

## Proof

- **Stress:** `server/copilot.contract.test.ts` passed 10 of 10 on CI's
Node 22 and herdr 0.9.3.
- **Local note:** this file cannot run on herdr 0.9.5 with Node 24,
because the stand-in's process shows as `MainThread` and herdr never
names it Copilot. The runs above use CI's versions.
- **Local checks:** `bun run check fast` ok.

Test-only. Splits: #2222 and #2223.

## Reviews

- **omo-native-code-reviewer**, round 1 on the combined change: BLOCK
with two HIGHs, both fixed. One was a loop shape that
`scripts/conventions.test.ts` counts as a fixed sleep; the other was the
OpenCode helper not waiting for its own Ctrl-L. Round 2 on these heads:
APPROVE, no findings above LOW.
- Test-only, so no Codex review (notes/pr-policy.md: the test-only gate
is omo alone).
@devswha

devswha commented Oct 28, 2026

Copy link
Copy Markdown
Owner Author

Merging ffa7612, a test-only fix from lane main-integration for the 'first message bound to the agent process' Integration failure seen on main. The test now starts the first message's same-kind replacement before the first agent ends. Codex reviewed it and found no weakened assertion, no production change and no fixed sleep, and its mocked launcher probes support the stated failure mechanism. The required checks are green, and CodeRabbit did not review this head. A LOW timing risk in a neighbouring, unchanged test goes to a follow-up issue.

@devswha
devswha merged commit 9b17565 into main Oct 28, 2026
20 checks passed
@devswha
devswha deleted the fix/main-integration-failures branch October 28, 2026 08:19
devswha added a commit that referenced this pull request Oct 28, 2026
…omplete (#2223)

Found by the main-integration lane: Integration failed on main run
40643422650 (7dc187a).

## Cause (test flake from #1991's test order, not a regression from
#2185 or #2200)

`refuses a confirmed Tab agent switch during immediate delivery without
Enter` runs right after `delivers a queued message completely when Tab
leaves the skill unconfirmed`, and that test leaves @effect's
autocomplete row selected (the recorded screen whose status row names no
agent). The stand-in redraws only when it is typed to. If herdr has not
yet drawn the screen that the next send's own `@effect` produces,
`typeWithSkills` (`server/opencode-skill-attach.ts`) reads the old box.
The agent before Tab is then unknown, and the Build to Plan switch
cannot be seen, so the send succeeds. The queue case then fails on the
outcome the immediate case left behind.

**Reproduced:** delaying the stand-in's `@effect` redraw by 400 ms fails
both cases 3 of 3, with CI's `ok: true` and the recorded outcome.

## Change

- The stand-in draws the recorded closed-autocomplete screen on Ctrl-L.
- `closedPrompt()` sends Ctrl-L, then waits until the stand-in has
logged it and herdr shows no selected @effect row. Both Tab-switch cases
call it before they note what was typed.

## Proof

- **With the fix:** the same 400 ms delay passes 4 of 4, and 3 of 3
again after review.
- **Stress:** `server/opencode.contract.test.ts` (24 tests) passed 10 of
10 on CI's Node 22 and herdr 0.9.3. That includes #2200's `closes the
shared stream when its last verified pane ends or stops being OpenCode`.
Its failure in run 40639664878 was #2200's pre-merge head, which still
had the stop-then-create test that the merged version replaced.
- **Local checks:** `bun run check fast` ok.

Test-only. Splits: #2222 and #2224.

## Reviews

- **omo-native-code-reviewer**, round 1 on the combined change: BLOCK
with two HIGHs, both fixed. One was a loop shape that
`scripts/conventions.test.ts` counts as a fixed sleep; the other was the
OpenCode helper not waiting for its own Ctrl-L. Round 2 on these heads:
APPROVE, no findings above LOW.
- Test-only, so no Codex review (notes/pr-policy.md: the test-only gate
is omo alone).
devswha added a commit that referenced this pull request Oct 29, 2026
…the first one ends (#2380)

Found by the flake-hunt lane: Integration failed and passed on a rerun
with no code change on 2026-10-28 13:24Z (job 122843077413, after #2222
fixed the sibling test the same way):
`server/pending-input.contract.test.ts` "a create request's first
message > queued while the shell was still in front, is bound to the
agent the launch brought up: a same-kind replacement before its page
attaches gets it held, never typed" expected `pending_target_changed`
and received `first_message_launch_ended`.

## Cause (test flake)

The test brought the launched agent up with `agent; exec agent2` and
killed the first one. Until the shell ran `exec`, the shell alone was in
front. If a status event arrived in that gap, the drain's
`learnLaunched` read the shell back in front (`launchGone`'s shell-back
branch in `server/core/pending.ts`) and rightly held the first message
with `first_message_launch_ended`. A held reason is never revised by
later events, so the test's `pending_target_changed` failed. Both
reasons hold the message and never type it, so this is not a delivery
bug.

**Reproduced:** with a builtin gap (`read -t 1`, which keeps the shell
itself in front) and an idle/working report during it, the old test
fails 5/5 with CI's exact `Received: "first_message_launch_ended"`. A
`sleep 1` gap does not reproduce it: then `sleep` is in front, and the
read counts as a target change.

## Change

As #2222 does for the sibling test, the replacement starts through the
launcher's SIGUSR1. The launcher ends the first agent only once the
second runs, so a claude process is in front throughout. The test then
waits until herdr no longer lists the first agent, and checks that the
replacement is a live foreground agent with another pid.

## Proof

- **Fail-first:** with the gap and the status events, the old test fails
5/5.
- **After:** with the same idle/working events fired during the
replacement, the new test passes 5/5. Plain runs pass 10/10, then 3/3
after the review's control-line change.
- **Whole file:** `server/pending-input.contract.test.ts` passes 105/0.
- **Local checks:** `bun run check fast` ok. One earlier run failed only
on the `server/codex-processes.test.ts` READY-line flake, which this PR
does not touch and which is fixed separately; the rerun was ok.

Test-only.

## Not reproduced (noted, not fixed here)

- The same file's `:475` "queued command was not recorded" (the "see
C:\temp\" test, once on main 2026-10-29 01:09Z): the recorder saw no CR
within 8 s, and the log holds no state to tell an unsent Enter from a
missed notification.

## Reviews

- **omo-native-code-reviewer:** APPROVE, CLEAR. It confirmed the cause
in `launchGone`/`learnLaunched`. As a mutation check, it stopped
`learn()` from recording the launched process, and the test failed with
the message typed into the replacement, so it still tests "never typed".
- LOW-1 (the control line also passed with no claude listed) was taken:
the test now checks the replacement exists first.
- LOW-2: the launcher command repeats `setup()`'s, because this test
creates its pane through `/api/tab/create`.
- Test-only, so no Codex review (notes/pr-policy.md).
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