[FEATURE]

Resolved 💬 1 comment Opened May 11, 2026 by macintacos Closed May 12, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

There's no way to hook into plan rejection in Claude Code. When a user rejects a plan in plan mode (with or without feedback), no hook fires that exposes the rejected plan or the rejection feedback. This prevents workflows like:

  • Logging rejected plans with the user's reasoning to a rejections.md or similar
  • Posting rejected plans to an issue tracker for follow-up
  • Building a dataset of "what I usually push back on" to feed back into CLAUDE.md or skills
  • More customized interactions that could be queue'd off of a plan rejection

Existing hooks don't cover this:

  • PermissionDenied only fires for auto-mode classifier denials, not manual rejection (per the hooks reference)
  • PostToolUse matched on ExitPlanMode only fires on approval
  • PermissionRequest can intercept the dialog but can't observe the outcome when the user rejects

Proposed Solution

Add a PlanRejected hook event that fires when the user rejects an ExitPlanMode request. Suggested payload:

{
  "session_id": "abc123",
  "hook_event_name": "PlanRejected",
  "transcript_path": "...",
  "plan": "<rejected plan content from ExitPlanMode.input.plan>",
  "feedback": "<user's rejection message, if any>"
}

Related to but distinct from #14259, which proposes pre/post plan lifecycle events generally — this is specifically the rejection branch, with the user's feedback surfaced as part of the payload.

Alternative Solutions

Workaround via UserPromptSubmit + transcript inspection (a hack). Listen on UserPromptSubmit, scan the transcript for the most recent ExitPlanMode tool_use with no matching tool_result, and treat the incoming prompt as the rejection feedback:

#!/bin/bash
# ~/.claude/hooks/plan-rejection.sh
INPUT=$(cat)
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path')
PROMPT=$(echo "$INPUT" | jq -r '.prompt')

# Pull the most recent ExitPlanMode plan from the transcript.
# (A real check would also verify no matching tool_result exists.)
LAST_PLAN=$(jq -rs '
  [.[] | select(.message.content[]?.type == "tool_use"
    and .message.content[].name == "ExitPlanMode")][-1]
  | .message.content[] | select(.name=="ExitPlanMode") | .input.plan
' "$TRANSCRIPT")

if [[ -n "$LAST_PLAN" ]]; then
  {
    echo "## $(date -Iseconds)"
    echo "### Rejected plan"
    echo "$LAST_PLAN"
    echo "### Feedback"
    echo "$PROMPT"
  } >> "${CLAUDE_PROJECT_DIR:-.}/.plans/rejections.md"
fi
exit 0
{
  "hooks": {
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "bash ~/.claude/hooks/plan-rejection.sh" }] }
    ]
  }
}

This works today but is brittle: it relies on transcript shape, a heuristic for "unresolved ExitPlanMode," and it can't distinguish a true rejection from other prompt-submission scenarios without more careful state tracking. A first-class PlanRejected event would make this deterministic and pass the rejection feedback explicitly.

Stop hook + sentinel file. Stamp a sentinel when PreToolUse:ExitPlanMode fires, then check for it on Stop / UserPromptSubmit. More moving parts than the transcript-scan approach and same fundamental fragility.

Priority

High - Significant impact on productivity

Feature Category

API and model interactions

Use Case Example

_No response_

Additional Context

_No response_

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗