Skip to content

fix(headless): honor can_use_tool deny interrupts - #3592

Open
GautamSharma99 wants to merge 1 commit into
letta-ai:mainfrom
GautamSharma99:fix/headless-deny-interrupt
Open

fix(headless): honor can_use_tool deny interrupts#3592
GautamSharma99 wants to merge 1 commit into
letta-ai:mainfrom
GautamSharma99:fix/headless-deny-interrupt

Conversation

@GautamSharma99

Copy link
Copy Markdown

Summary

Fixes #3582.

Bidirectional headless clients can already send interrupt: true with a denied can_use_tool response, but the runtime previously discarded that field and continued the approval flow. This change carries the interrupt intent into the active turn lifecycle and terminates the turn through its existing AbortController.

What changed

  • Extracted the permission-response wait loop into a focused headless-permission owner module.
  • Added an explicit interrupted state to the local permission result.
  • Abort the active turn synchronously when the matching deny response has interrupt: true.
  • Break out of the approval loop before emitting local tool events, executing any decision batch, or starting another provider round.
  • Continue to treat omitted or false interrupt values as ordinary denials.
  • Preserve unrelated and malformed stdin lines in their original order for the main loop.
  • Correlate responses to the active permission request so a stale request ID cannot interrupt the current turn.
  • Updated the public protocol comment now that the field is wired up.
  • Updated the deterministic Bash fixture with its required description argument so the end-to-end approval path is exercised rather than auto-denied during validation.
  • Reduced src/headless.ts by 51 lines and lowered its source-size baseline from 5,243 to 5,192.

Lifecycle and ordering

For an interrupting denial, the runtime does not synthesize or emit a denied tool return. It aborts the current controller and exits the approval loop immediately. The turn emits one terminal result with subtype interrupted, and no tool execution or provider continuation occurs for that response. If the backend still has a dangling approval, the existing post-interrupt recovery path resolves it before the next user turn.

This ordering prevents an auto-approved sibling decision in the same approval batch from executing after the client requested a stop.

Tests

Added focused coverage for:

  • deny without interrupt, which retains the current continuation behavior;
  • deny with interrupt: true, including abort-before-return ordering;
  • stale interrupting responses, which are deferred and cannot abort a newer request;
  • preservation and ordering of other deferred stdin lines;
  • a two-turn child-process protocol scenario proving:
    • ordinary deny emits a tool return and continues to a provider result;
    • interrupting deny emits exactly one interrupted result;
    • no tool return or assistant/provider message is emitted for the interrupted approval.

Validation completed:

  • bun test src/headless-permission.test.ts src/headless-interrupt-latch.test.ts src/backend/fake-headless-backend.test.ts — 16 passed, 0 failed
  • bun run check — all 12 checks passed

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.

Headless can_use_tool deny responses ignore the interrupt flag

2 participants