Found on the first live end-to-end run. Planning failed (Opus 5.5 rejected forced tool use, since fixed in LiteLLM's model_info), the agent asked how to proceed, and a restart to pick up the LiteLLM change left it stuck: no message could move it.
Causes (nisse-dev/src/nisse_dev/control.py)
Boot treated AWAIT_INPUT as an interruption mid-turn and replaced the saved pending item with a generic {"kind": "boot"} asking "continue or re-plan?".
Nothing handled a boot answer: the catch-all said "noted", cleared the pending item, and stayed in AWAIT_INPUT. From there every reply got "no pending item … try 'use '", but use only works from IDLE.
Fix
On boot in AWAIT_INPUT with a pending item, ask the saved question again (A3 step 8: resume the last committed state). _await_input now stores its prompt with the pending item.
The restart question (A3 step 7) records the interrupted state. continue re-runs an interrupted planning turn, or returns an interrupted milestone to its approved plan for start milestone N. re-plan plans the current step again. Anything else asks again.
With nothing pending, use <project> starts over.
Tests: five new cases in test_control.py, including the live run's exact sequence (planning fails → restart → reply resumes) and the leftover state it produced. All five fail on main and pass here. 425 unit tests pass; lint and format clean.
Not in this PR, noticed while reading: a restart in PR_OPEN also gets the generic restart question although it is a waiting state, and boot doesn't re-attach the Gitea plan/review pollers, so Gitea approvals and review comments after a restart look unwatched until the next plan post. Both are worth their own follow-up.
Found on the first live end-to-end run. Planning failed (Opus 5.5 rejected forced tool use, since fixed in LiteLLM's model_info), the agent asked how to proceed, and a restart to pick up the LiteLLM change left it stuck: no message could move it.
**Causes** (`nisse-dev/src/nisse_dev/control.py`)
1. Boot treated `AWAIT_INPUT` as an interruption mid-turn and replaced the saved pending item with a generic `{"kind": "boot"}` asking "continue or re-plan?".
2. Nothing handled a `boot` answer: the catch-all said "noted", cleared the pending item, and stayed in `AWAIT_INPUT`. From there every reply got "no pending item … try 'use <project>'", but `use` only works from `IDLE`.
**Fix**
- On boot in `AWAIT_INPUT` with a pending item, ask the saved question again (A3 step 8: resume the last committed state). `_await_input` now stores its prompt with the pending item.
- The restart question (A3 step 7) records the interrupted state. `continue` re-runs an interrupted planning turn, or returns an interrupted milestone to its approved plan for `start milestone N`. `re-plan` plans the current step again. Anything else asks again.
- With nothing pending, `use <project>` starts over.
**Tests:** five new cases in `test_control.py`, including the live run's exact sequence (planning fails → restart → reply resumes) and the leftover state it produced. All five fail on `main` and pass here. 425 unit tests pass; lint and format clean.
**Not in this PR, noticed while reading:** a restart in `PR_OPEN` also gets the generic restart question although it is a waiting state, and boot doesn't re-attach the Gitea plan/review pollers, so Gitea approvals and review comments after a restart look unwatched until the next plan post. Both are worth their own follow-up.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A restart in AWAIT_INPUT replaced the saved pending item with a generic
"continue or re-plan?" whose answer nothing handled: the reply cleared
it and left the agent in AWAIT_INPUT with nothing to resume, where 'use'
was refused too. The first live run hit this after a planning failure
and could not be moved by any message.
- Waiting on the operator is a committed state (A3 step 8): on boot, ask
the saved question again instead of replacing it. _await_input keeps
the prompt with the pending item for this.
- The restart question (A3 step 7) records the state it interrupted, and
'continue' / 're-plan' now do what they offer: re-run the interrupted
planning turn, or return an interrupted milestone to its approved plan.
Any other reply asks again rather than being swallowed.
- With nothing pending, 'use <project>' starts over, as the reply
already advised.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found on the first live end-to-end run. Planning failed (Opus 5.5 rejected forced tool use, since fixed in LiteLLM's model_info), the agent asked how to proceed, and a restart to pick up the LiteLLM change left it stuck: no message could move it.
Causes (
nisse-dev/src/nisse_dev/control.py)AWAIT_INPUTas an interruption mid-turn and replaced the saved pending item with a generic{"kind": "boot"}asking "continue or re-plan?".bootanswer: the catch-all said "noted", cleared the pending item, and stayed inAWAIT_INPUT. From there every reply got "no pending item … try 'use '", butuseonly works fromIDLE.Fix
AWAIT_INPUTwith a pending item, ask the saved question again (A3 step 8: resume the last committed state)._await_inputnow stores its prompt with the pending item.continuere-runs an interrupted planning turn, or returns an interrupted milestone to its approved plan forstart milestone N.re-planplans the current step again. Anything else asks again.use <project>starts over.Tests: five new cases in
test_control.py, including the live run's exact sequence (planning fails → restart → reply resumes) and the leftover state it produced. All five fail onmainand pass here. 425 unit tests pass; lint and format clean.Not in this PR, noticed while reading: a restart in
PR_OPENalso gets the generic restart question although it is a waiting state, and boot doesn't re-attach the Gitea plan/review pollers, so Gitea approvals and review comments after a restart look unwatched until the next plan post. Both are worth their own follow-up.🤖 Generated with Claude Code