Skip to content
This repository was archived by the owner on Sep 4, 2026. It is now read-only.
This repository was archived by the owner on Sep 4, 2026. It is now read-only.

ExitPlanMode hits the same generic Allow/Deny prompt as AskUserQuestion did #396

Description

@ElbertePlinio

Split out of #364 as that issue's Notes said to.

ExitPlanMode also declares requiresUserInteraction(), and that check runs
before the bypass branch in the SDK's permission evaluator — the same reason
AskUserQuestion routed through canUseTool even under Bypass. So it reaches
the host as an ordinary approval and renders through ApprovalPrompt's generic
path.

headline() resolves a non-Bash tool to its bare name, so the user sees
TOOL ExitPlanMode and nothing else: not the plan being proposed, and no
indication that accepting means leave plan mode and start executing.

Why this is worth its own fix

#364 established the shape. ApprovalKind::Question and the payload channel
through chat_approveapproveagent_chat_approve already exist, and
ApprovalPrompt already dispatches per-kind. So this is a renderer plus a kind,
not new plumbing.

But the contract is different and needs checking against the SDK rather than
assumed: ExitPlanMode carries the plan text as its input, and what the tool
expects back on approval is not the answers shape. Read the SDK's own TUI
handler before designing the response
, the way #364 did — guessing the shape
is exactly how that bug shipped.

Scope

  • ApprovalKind::Plan (or reuse Question if the response shape turns out to
    match — decide from the SDK, not from symmetry)
  • approval_kind("ExitPlanMode") mapping
  • surface the plan text through parseApprovalDetail
  • a renderer that shows the plan and makes accept/reject mean what they do
  • exempt from alwaysAllow for the same reason AskUserQuestion is:
    approvalScopeKey collapses every call to ExitPlanMode\0"", so one session
    grant would auto-approve every future plan exit silently

Validation

The live tier from #394 is the right home for the end-to-end proof:
PICKFORGE_E2E_CLAUDE=1 bun run e2e:claude already drives the real CLI through
the real bridge. Force a plan-mode turn, accept the exit, and assert the tool's
own result — the same load-bearing check that caught #364's regression.

Refs #364

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-ideAgent IDE last-milebugSomething isn't workinguxUX / interaction polish

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions