Skip to content

Don't show Claude Code's injected turns as the notification query - #79

Open
kadufarah1979 wants to merge 1 commit into
warpdotdev:mainfrom
kadufarah1979:fix/notification-shows-injected-turns
Open

Don't show Claude Code's injected turns as the notification query#79
kadufarah1979 wants to merge 1 commit into
warpdotdev:mainfrom
kadufarah1979:fix/notification-shows-injected-turns

Conversation

@kadufarah1979

@kadufarah1979 kadufarah1979 commented Aug 13, 2026

Copy link
Copy Markdown

What happens

When a turn ends because a background task reported back, the Warp notification shows raw markup instead of the prompt:

<task-notification>
<task-id>bpzyv8z2j</task-id>
<summary>Monitor event: "pipeline for MR !1151 rea...
Task completed.

Why

on-stop.sh builds query from the last transcript entry of type: "user". Not every such entry is something a human typed — Claude Code injects turns of its own with the same type: background task notifications, skill loads, slash-command expansions, compaction summaries. When one of those is the last user entry, its raw markup becomes the notification title.

Counting user entries across my own transcripts:

origin.kind entries content
human 96 the actual prompt
task-notification 42 <task-notification>…
absent, isMeta: true 16 skill loads, <local-command-stdout>, [Image: …]

So Claude Code already marks the difference — the hook just wasn't reading it.

The change

Key on origin.kind == "human" rather than on type alone. The extraction was duplicated in on-stop.sh and on-stop-failure.sh, so it moves into a shared extract-query.sh; transcripts predating the origin field fall back to the last non-meta turn whose text isn't injected markup, so nothing regresses for older clients.

Tests

Six cases added to plugins/warp/tests/test-hooks.sh, covering task notifications, isMeta turns, tool results, the no-origin fallback, an all-injected transcript, and a missing transcript. Each one fails against main and passes here.

=== Results: 63 passed, 0 failed ===

Also verified end to end — on-stop.sh with a mock hook payload now emits "query":"review the pipeline for MR !1151" where it previously emitted the <task-notification> block.

🤖 Generated with Claude Code

The Stop and StopFailure hooks build the notification title from the last
transcript entry of type "user". Not every such entry is something a human
typed: Claude Code injects turns of its own with the same type — background
task notifications, skill loads, slash-command expansions, compaction
summaries. When a turn ends because one of those arrived, the notification
shows raw markup instead of the prompt:

    <task-notification>
    <task-id>bpzyv8z2j</task-id>
    <summary>Monitor event: "pipeline for MR !1151 rea...

Claude Code already distinguishes the two: real prompts carry
origin.kind == "human", injected turns carry another origin kind (or no
origin plus isMeta: true). Key on that instead of on type alone.

The extraction was duplicated in on-stop.sh and on-stop-failure.sh, so it
moves to a shared extract-query.sh. Transcripts predating the origin field
fall back to the last non-meta turn whose text isn't injected markup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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