Skip to content

Add proposal scraper Stop hook example#48714

Open
WGlynn wants to merge 1 commit intoanthropics:mainfrom
WGlynn:add-proposal-scraper-hook-example
Open

Add proposal scraper Stop hook example#48714
WGlynn wants to merge 1 commit intoanthropics:mainfrom
WGlynn:add-proposal-scraper-hook-example

Conversation

@WGlynn
Copy link
Copy Markdown

@WGlynn WGlynn commented Apr 15, 2026

Summary

Adds a second example to examples/hooks/ — a Stop hook that persists proposal blocks (options, alternatives) from assistant responses to a PROPOSALS.md file so they survive session crashes, context compaction, and API errors.

Alongside the existing bash_command_validator_example.py (a PreToolUse hook), this demonstrates a different hook type (Stop) and a different use case (output persistence vs input validation).

Motivation

When an assistant proposes options for a user to choose between — **Option A** / **Option B** / **Option C**, or a numbered decision slate — and the session crashes before the user decides, the options exist only in the chat transcript. Regenerating them in a new session produces different options because LLM sampling is non-deterministic. Persisting proposals the moment they are generated makes the file the source of truth and the chat a view.

Detection

The hook uses two layers of detection:

  • Strong signal (auto-trigger): explicit option labels (**Option A**, cycle-style IDs like **C11-A**, or Option A: prose) appearing outside of code spans.
  • Weak signal: two or more numbered bold items in combination with a proximity keyword (options, propose, alternative, pick one, which of, choose). This prevents false positives on structured summaries that happen to use numbered bold lists.

Matches inside backtick code spans are stripped before detection so that documentation of the trigger patterns (including this PR description) does not self-trigger.

Behavior

  • Reads the session transcript via the transcript_path field from the hook context.
  • Extracts the last assistant message text.
  • If it looks like a proposal, appends to .claude/PROPOSALS.md (project-local) or falls back to the current working directory.
  • Fail-open: any error returns cleanly without blocking the turn.

Testing

The hook was developed iteratively and has been tested against a corpus of real proposal and non-proposal content, including:

  • Explicit **Option A** / **Option B** blocks (triggers)
  • Cycle-style **C11-A** labels (triggers)
  • Numbered bold lists with "options" / "propose" keywords (triggers)
  • Completion summaries using numbered bold lists without keywords (does not trigger)
  • Documentation mentioning trigger patterns inside backticks (does not trigger)
  • Plain questions and answers (does not trigger)

Style

Follows the convention of bash_command_validator_example.py:

  • Self-contained, stdlib-only
  • Shebang + docstring with settings.json snippet
  • Fail-open error handling
  • "Change your path to your actual script" note in the docstring

Configuration

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/claude-code/examples/hooks/proposal_scraper_example.py"
          }
        ]
      }
    ]
  }
}

Adds examples/hooks/proposal_scraper_example.py — a Stop hook that
persists option/alternative blocks from assistant responses to a
PROPOSALS.md file so they survive session crashes, context compaction,
and API errors.

Motivation: when an assistant proposes options (Option A/B/C, numbered
alternatives) and a session crashes before the user decides, the options
exist only in the chat transcript. Regenerating them in a new session
produces different options because LLMs are non-deterministic. Persisting
proposals the moment they are generated makes the file the source of
truth and the chat a view.

Detection heuristics:
- Strong signal (auto-trigger): explicit option labels (Option A/B/C,
  cycle IDs like C11-A, "Option A:") outside of code spans.
- Weak signal: two or more numbered bold items plus a proximity keyword
  (options, propose, alternative, pick one, which of, choose).
- Matches inside backtick code spans are ignored so that documentation
  of the trigger patterns does not self-trigger.

The hook is fail-open: any error returns cleanly without blocking the turn.

This adds a second example to examples/hooks/ (alongside the existing
bash_command_validator_example.py) demonstrating a different hook type
(Stop vs PreToolUse).
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