Skip to content

fix: generic interrupt() resumes with the raw user value, not {"decisions"} (gh #99) - #100

Merged
dkedar7 merged 1 commit into
mainfrom
fix/generic-interrupt-resume-99
Jul 26, 2026
Merged

dkedar7 merged 1 commit into
mainfrom
fix/generic-interrupt-resume-99

Conversation

@dkedar7

@dkedar7 dkedar7 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Problem

#82 and #95 fixed displaying a generic interrupt(...) payload. This closes the matching resume gap.

The CLI built the resume envelope unconditionally as {"decisions": [...]} — the deepagents/langchain tool-review protocol — and forwarded it verbatim as command.resume, which becomes the return value of interrupt(). So the canonical LangGraph "collect input from a human" pattern:

name = interrupt("What is your name?")
return AIMessage(content=f"Hello, {name}!")

received {"decisions": [{"type": "approve"}]} and produced the visibly wrong Hello, {'decisions': [{'type': 'approve'}]}!. Even "Provide custom decision (JSON)" wrapped the typed value inside the envelope, so there was no workaround.

Fix

Branch the resume on the same signal #82/#95 use to render (_is_generic_interrupt, mirroring format_interrupt_request):

  • deepagents tool-review interrupt (an action_request dict keyed action, or legacy tool) → keeps the {"decisions": [...]} envelope, unchanged.
  • generic / scalar interrupt(value) (a bare string like "What is your name?", or a plain dict without an action key) → resumes with the raw value the user enters, unwrapped. JSON is parsed when it parses (so a number/list/dict survives); otherwise the text is used verbatim as a string.
  • --no-interactive on a generic interrupt → resumes with an empty value rather than injecting a fake approval it never asked for, keeping the run-to-completion contract.

Tests

New tests/test_generic_interrupt.py drives real agents through the CLI resume path:

test_no_interactive_approve.py's fixture now uses a genuine tool-review interrupt so it keeps testing auto-approve precisely; the generic auto path is covered in the new file.

Full suite: 151 passed. ruff check and ruff format --check clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_011HWCfJii6gXd3XL3Gq3W8B

…ions"} (gh #99)

The CLI built the resume envelope unconditionally as {"decisions": [...]} — the
deepagents tool-REVIEW protocol — and forwarded it verbatim as command.resume,
which becomes the return value of interrupt(). So the canonical LangGraph
"collect input from a human" pattern, name = interrupt("What is your name?"),
received {"decisions": [{"type": "approve"}]} and produced the visibly wrong
Hello, {'decisions': [{'type': 'approve'}]}!. Even "Provide custom decision
(JSON)" wrapped the typed value inside the envelope, leaving no workaround.

Branch the RESUME on the same signal #82/#95 use to RENDER: a deepagents
tool-review interrupt (an action_request dict keyed `action`, or legacy `tool`)
keeps the {"decisions": [...]} envelope unchanged; a generic/scalar
interrupt(value) resumes with the raw value the user enters, UNWRAPPED (JSON is
parsed when it parses; otherwise the text is used verbatim). Under
--no-interactive a generic interrupt resumes with an empty value rather than
injecting a fake approval, keeping the run-to-completion contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011HWCfJii6gXd3XL3Gq3W8B
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