Skip to content

fix: unflake the concurrency test; escalate a livelock reroute that stopped working (#30) - #37

Merged
ulmentflam merged 2 commits into
mainfrom
nightly/keepalive-escalation-ci-fix
Jul 28, 2026
Merged

fix: unflake the concurrency test; escalate a livelock reroute that stopped working (#30)#37
ulmentflam merged 2 commits into
mainfrom
nightly/keepalive-escalation-ci-fix

Conversation

@ulmentflam

@ulmentflam ulmentflam commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Two fixes, both independent of the RFC 007/012 follow-on work still sitting on a local branch.

1. CI: test_run_loop_concurrency_actually_parallel no longer times the loop

The test asserted elapsed < 0.25 and CI failed at 0.964s. That ceiling had already been raised once — from 0.18s, after a 0.182s flake — and raising it again does not work: any threshold generous enough to survive a loaded runner is also generous enough to pass a fully serial run, whose baseline is 0.2s. The test would keep passing and stop meaning anything.

It now measures concurrency structurally. _TrackingHost counts dispatches in flight; the test asserts the peak is greater than 1 (they genuinely overlapped) and no greater than the configured concurrency. That is immune to runner load and strictly stronger than the duration check — it also catches over-concurrency, which elapsed time never could, plus a dispatch leaking past the end of the run.

Mutation-verified: with concurrency=1 the new assertion fails with "dispatches ran serially — concurrency had no effect". The old assertion would have passed that same mutation on a fast machine, which is the whole problem.

2. Issue #30: the livelock reroute now escalates when it stops working

Closes #30.

The issue reports involuntary mid-chain stops leaving only a RESPAWN_REQUESTED marker. Its attached keepalive log contains the cause: 130 consecutive identical livelock reroutes, one every 10-30 seconds, context growing ~1700 tokens per turn. The agent emits a sentence and stops; the hook blocks and injects byte-identical text; repeat.

That spin is not merely unproductive — it is the proximate cause of the reported symptom. Burning the host's without-progress budget on 130 no-op turns is what gets the session killed. The v0.0.11 reroute detects a repeated pick but never notices that its own remedy has failed, so it re-sends an instruction that has demonstrably not worked, indefinitely.

After _LIVELOCK_REPICKS + _LIVELOCK_ESCALATE_AFTER reroutes for the same pick, the injected prompt changes instead of repeating. It names the specific stuck pick — the generic text never said which one — and gives the agent a way to discharge it:

  1. Verify the deliverable exists.
  2. If it does, tick the box and commit the reconciliation.
  3. If it is unactionable for another reason, record why.
  4. Only then pick different work.

Step 2 matters most. "Treat it as nothing" told the agent to ignore an item it believed was real, without offering any action that would make it go away — so the next boundary produced the same pick. A reconciliation commit is real work and it removes the pick from the cascade.

The log line now distinguishes the escalated case, so this is diagnosable from keepalive.log alone next time.

Deliberately unchanged: the session is never released. Only human disk markers terminate a run, and a spinning session is still a session.

Verification

1167 tests pass (5 new). nightly verify clean on all five checks: ruff-check, ruff-format, pyrefly, make-lint, make-check.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of repeated stalled actions during automated workflows.
    • Keepalive guidance now detects when rerouting is ineffective and provides clearer instructions to verify progress and reconcile outstanding work.
    • Updated status messages to indicate when enhanced escalation is active.
  • Tests

    • Added coverage for escalation guidance, malformed inputs, and concurrency behavior to improve reliability.

ulmentflam and others added 2 commits July 28, 2026 16:46
CI failed at 964ms against a 250ms ceiling. That ceiling had already been
raised once — from 180ms, after it flaked at 182ms — and raising it again
does not work: any threshold generous enough to survive a loaded runner
is also generous enough to pass a fully serial run, whose baseline is
200ms. The test would keep passing and stop meaning anything.

So it no longer times the loop. `_TrackingHost` counts dispatches in
flight and the test asserts the peak: greater than 1 (they genuinely
overlapped) and no greater than the configured concurrency. Immune to
runner load, and strictly stronger than the duration check — it also
catches *over*-concurrency, which elapsed time never could, plus a
dispatch leaking past the end of the run.

Verified by mutation: with `concurrency=1` the new assertion fails with
"dispatches ran serially — concurrency had no effect". The old assertion
would have passed that same mutation on a fast machine, which is the
whole problem.

The `asyncio.sleep(0.05)` stays — it creates the window in which overlap
can be observed — but no assertion depends on how long it takes.

1498 tests pass; ruff, pyrefly, and `nightly verify` clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Issue #30 reports involuntary mid-chain stops that leave only a
RESPAWN_REQUESTED marker. Its attached keepalive log contains the cause:
**130 consecutive identical livelock reroutes**, one every 10-30 seconds,
context growing ~1700 tokens per turn. The agent emits a sentence and
stops; the hook blocks and injects byte-identical text; repeat.

That spin is not merely unproductive — it is the proximate cause of the
reported symptom. Burning the host's without-progress budget on 130
no-op turns is what gets the session killed. The v0.0.11 reroute detects
a repeated pick but never notices that its own remedy has failed, so it
re-sends an instruction that has demonstrably not worked, indefinitely.

After `_LIVELOCK_REPICKS + _LIVELOCK_ESCALATE_AFTER` reroutes for the
same pick, the injected prompt changes instead of repeating. It names the
specific stuck pick — the generic text never said *which* one — and gives
the agent a way to discharge it:

1. Verify the deliverable exists (rule 13's pre-flight check).
2. If it does, tick the box and commit the reconciliation.
3. If it is unactionable for another reason, record why.
4. Only then pick different work.

Step 2 matters most. "Treat it as `nothing`" told the agent to ignore an
item it believed was real, without offering any action that would make it
go away — so the next boundary produced the same pick. A reconciliation
commit is real work *and* it removes the pick from the cascade.

The log line now distinguishes the escalated case, so this is
diagnosable from `keepalive.log` alone next time.

Deliberately unchanged: the session is never released. Only human disk
markers terminate a run, and a spinning session is still a session.

1498 tests pass (5 new); ruff, pyrefly, and `nightly verify` clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The keepalive hook now escalates repeated livelock reroutes with a verification and reconciliation prompt. Keepalive messages report escalation status. Driver concurrency coverage now measures actual overlapping dispatches instead of elapsed time.

Changes

Keepalive livelock escalation

Layer / File(s) Summary
Escalation prompt and reroute handling
packages/nightly-core/src/nightly_core/keepalive_hook.py, packages/nightly-core/tests/test_keepalive_context.py
Adds an escalation threshold and _spin_escalation_block, integrates it into stop-hook decisions, updates keepalive messaging, exports the helper, and tests prompt content and malformed choices.

Concurrency test validation

Layer / File(s) Summary
Overlap-based concurrency assertion
packages/nightly-core/tests/test_driver.py
Tracks simultaneous run_headless calls and asserts the observed peak is greater than one, no greater than two, and zero after completion.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StopHook as Stop-hook decision
  participant Cascade as Cascade choice
  participant Agent
  StopHook->>Cascade: inspect repeated eligible pick
  StopHook->>Agent: inject escalation verification and reconciliation prompt
  Agent-->>StopHook: continue with updated decision
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR only partially addresses #30 by escalating repeated reroutes; it does not add the watchdog, auto-resume, or daemon-supervisor behavior. Implement the stop-hook watchdog, automatic resume/reinvoke flow, and RFC 010 daemon-supervisor behavior required by #30.
Out of Scope Changes check ⚠️ Warning The concurrency test unflake is unrelated to #30 and appears outside the linked issue's stop-hook and auto-recovery scope. Remove or separate the concurrency-test changes into a different PR focused on that test fix.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes both the concurrency test fix and the livelock reroute escalation, matching the PR changes.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nightly/keepalive-escalation-ci-fix

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.

@ulmentflam
ulmentflam merged commit f91a41f into main Jul 28, 2026
2 of 3 checks passed
@ulmentflam
ulmentflam deleted the nightly/keepalive-escalation-ci-fix branch July 28, 2026 20:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/nightly-core/src/nightly_core/keepalive_hook.py`:
- Around line 265-274: Update the escalation guidance in the keepalive prompt
construction around the existing reconciliation steps so it branches by pick
source: retain RFC checklist/commit reconciliation for RFC picks, and provide
github_issue picks with an appropriate issue/evidence reconciliation action that
can discharge repeated work without RFC paperwork. In
packages/nightly-core/tests/test_keepalive_context.py lines 293-305, add a
github_issue case and assert the source-appropriate reconciliation guidance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a5d06180-4b44-4f1d-8e39-5fe067a77c42

📥 Commits

Reviewing files that changed from the base of the PR and between 3264dff and ce023d6.

📒 Files selected for processing (3)
  • packages/nightly-core/src/nightly_core/keepalive_hook.py
  • packages/nightly-core/tests/test_driver.py
  • packages/nightly-core/tests/test_keepalive_context.py

Comment on lines +265 to +274
"1. Verify the pick is genuinely done or not actionable \u2014 check the "
"symbol/file exists, check `git log` and any unmerged `nightly/*` "
"branch, check open PRs. (Rule 13, pre-flight verification.)",
"2. If it IS already done: tick the checklist box and commit "
"`docs(rfc-NNN): tick <ITEM> \u2014 already implemented in <SHA>`. That "
"reconciliation is real work and it stops this loop.",
"3. If it is NOT actionable for another reason: record why in the task "
"plan or an approval note, so the next turn has evidence instead of "
"re-deriving it.",
"4. Only then pick different work.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make escalation reconciliation source-aware.

The prompt also runs for github_issue picks, but it unconditionally requires an RFC checklist and docs(rfc-NNN) commit. A repeated issue with no RFC cannot follow that discharge path, so it can remain stuck. Use source-neutral guidance or branch by source, then cover both paths.

  • packages/nightly-core/src/nightly_core/keepalive_hook.py#L265-L274: give github_issue picks an appropriate evidence/issue-reconciliation action instead of requiring RFC paperwork.
  • packages/nightly-core/tests/test_keepalive_context.py#L293-L305: add a github_issue case and assert its source-appropriate reconciliation guidance.
📍 Affects 2 files
  • packages/nightly-core/src/nightly_core/keepalive_hook.py#L265-L274 (this comment)
  • packages/nightly-core/tests/test_keepalive_context.py#L293-L305
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nightly-core/src/nightly_core/keepalive_hook.py` around lines 265 -
274, Update the escalation guidance in the keepalive prompt construction around
the existing reconciliation steps so it branches by pick source: retain RFC
checklist/commit reconciliation for RFC picks, and provide github_issue picks
with an appropriate issue/evidence reconciliation action that can discharge
repeated work without RFC paperwork. In
packages/nightly-core/tests/test_keepalive_context.py lines 293-305, add a
github_issue case and assert the source-appropriate reconciliation guidance.

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.

Involuntary stops recur + stop-hook monitor watchdog never launched in interactive session

1 participant