fix(warp): skip synthetic transcript entries when picking the notification title - #78
Open
bobrnor wants to merge 1 commit into
Open
Conversation
…ation title
The Stop and StopFailure hooks title Warp's notification with "the user's
last prompt", which they read from the session transcript. The filter only
excluded tool-result entries, on the assumption that every other `type:
"user"` entry is human-authored. Claude Code also writes its own injections
as user-type entries with ordinary text content, so those became the title:
<bash-stdout>arn:aws:ecs:us-east-1:123:task/prod-cluster/a4d9c84a37…
Task completed.
Local `!` bash output is the most visible case, but the same class covers
background subagent completions, slash-command wrappers, local command
output, system reminders and cross-session/teammate messages.
Move the transcript scraping into a sourceable extract-transcript.sh — which
also drops the copy of the filter that on-stop-failure.sh had to duplicate —
and skip entries that are confidently recognized as Claude Code internals,
falling back to the previous real prompt. Detection anchors on the ASCII
envelope tags rather than the surrounding English framing, which localized
builds may translate. Anything not positively recognized passes through
unchanged: an odd-looking title is better than a missing notification.
Verified against a real transcript that reproduced the screenshot above: the
title becomes the user's actual prompt instead of the ARN dump.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Q7WdVu4eYxgDbEweugNDT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
on-stop.sh(and, since #73,on-stop-failure.sh) titles the Warp notification with "the user's last prompt", scraped from the session transcript. The filter's only exclusion is tool-result entries — the comment aton-stop.sh:33-38states the assumption that every othertype: "user"entry is human-authored.That assumption doesn't hold. Claude Code writes a number of its own injections into the transcript as user-type entries with ordinary string content, so they sail through the filter and become the notification title:
The most visible source is local
!bash mode — running!aws ecs list-tasks …in Claude Code records<bash-input>…</bash-input>and<bash-stdout>…</bash-stdout><bash-stderr></bash-stderr>as two plain-string user entries. If the turn ends after that, the ARN dump is the title.Sampling 25 of my own recent transcripts, these user-type entries are all currently eligible to become a title:
<task-notification><local-command-stdout><command-name><system-reminder><bash-stdout>/<bash-input>Root cause
on-stop.sh:39-51andon-stop-failure.sh:28-40(identical copies of the same jq filter) select the lasttype == "user"entry whose content is a plain string or an array containing atextblock. Tool results are arrays oftool_resultblocks and drop out; every synthetic injection with text content does not.Fix
Move the transcript scraping into a sourceable
scripts/extract-transcript.sh— which also removes the duplicated filteron-stop-failure.shhad to carry — and skip entries positively recognized as Claude Code internals, falling back to the previous real prompt.Following the approach argued in #72:
<teammate-message>/<cross-session-message>arrive behind a "Another Claude session sent a message:" line that a localized build may translate, so those two are matched anywhere in the entry rather than as a prefix.[Request interrupted by user…],Stop hook feedback:) are best-effort: a localized build falls through to today's behaviour rather than breaking.When every candidate is filtered out,
QUERYis empty and Warp falls back to its own"<agent> completed"title, which is still an improvement on raw XML.legacy/on-stop.shis untouched — it uses the first user entry, which is the human's opening prompt, so it never hit this.Tests
18 cases added to
tests/test-hooks.shcovering each synthetic form, plus the cases that guard against over-filtering: a human prompt that merely mentions a tag mid-sentence is kept, tool results are still skipped, text blocks are still joined, and degenerate transcripts (all-synthetic, no user entries, missing path) return empty rather than erroring.shellcheck -S warningis clean on all three touched scripts.Verified end-to-end against the real transcript that produced the screenshot above — replaying it through the patched hook,
querygoes from the<bash-stdout>arn:aws:ecs:…dump to the user's actual prompt.Relationship to existing issues and PRs
bash-*; I replayed my reproduction through that PR's filter and<bash-stdout>still comes through. This PR keeps every tag fix(warp): skip synthetic system messages when picking last user prompt #59 covers, adds the missing ones, extends the fix toon-stop-failure.sh(which didn't exist when fix(warp): skip synthetic system messages when picking last user prompt #59 was opened), and adds test coverage. Happy to close this in favour of fix(warp): skip synthetic system messages when picking last user prompt #59 with the extra tags folded in, if the author prefers.<task-notification>re-invocations), spamming a banner per sub-agent #67 (<task-notification>from background subagents) and Stop hook surfaces raw <teammate-message> XML as the notification title when an Agent Teams message is the last user-type transcript entry #72 (<teammate-message>envelopes).${QUERY:0:197}truncation that splits multibyte UTF-8 is in the same block and worth fixing, but it's an independent bug; happy to send it separately.