Skip to content

fix(coding-agents): drop harness transport wrappers from retained turns (#3023) - #3378

Merged
nicoloboschi merged 1 commit into
mainfrom
fix/coding-agents-harness-transport-3023
Aug 11, 2026
Merged

fix(coding-agents): drop harness transport wrappers from retained turns (#3023)#3378
nicoloboschi merged 1 commit into
mainfrom
fix/coding-agents-harness-transport-3023

Conversation

@nicoloboschi

Copy link
Copy Markdown
Collaborator

Closes #3023.

What I found

The issue names two cases. Measured against 400 real Claude Code transcripts on this machine, both are already handled here — and a third one, which it doesn't name, is live:

#3023 claim in coding-agents why
skill bodies retained as user conversation no Claude Code marks them isMeta: true (8/8 in the sample); the reader drops isMeta lines
<system-reminder> retained no they arrive inside tool_result blocks, which the reader drops entirely
<task-notification> retained YES 39 in 400 transcripts, delivered as plain type:"user", isMeta:false, string body

So the harness's background-task plumbing — task id, tool-use id, status, summary — was being fed to fact extraction as things the user said.

Reproduction

Running the real readClaudeTranscript over a real transcript, before:

retained turns: 243
turns that are a harness task-notification: 1   (519 chars, role "user")
"<task-notification>\n<task-id>b46ca19nr</task-id>\n<tool-use-id>toolu_0127…</tool-use-id>\n<status>stopped</status>…"

After:

before: 243 turns, 1 harness-noise turn
after:  242 turns, 0 harness-noise turns
real turns preserved: true

The fix

One tag added to MEMORY_TAG_RE, which already covered codex's <hook_prompt> for exactly this reason — its comment calls them "the host's own hook-transport wrappers… transport noise, not the user's work". <task-notification> is that category for Claude Code.

<system-reminder> joins it as well. Not reachable today (dropped tool_result), but it is the same class and the rule is tag-structural rather than content-guessing, so listing it costs nothing and survives the harness moving where it puts them.

Stripping removes the block and keeps surrounding text: a message that is nothing but a wrapper renders empty and is dropped as a no-content turn, while a real message that merely mentions one keeps the user's words. That is deliberately the opposite of the old plugin's unanchored strip_channel_envelope, which replaced the whole message with the tag's inner text (#3124).

Test

433 passed, 19 skipped. tsc --noEmit and ./scripts/hooks/lint.sh clean.

Three new cases: a notification-only message producing no turn at all; a message with a wrapper embedded in real text keeping the text; and a <system-reminder> delivered as user text being stripped.

Closes #3023.

Claude Code delivers <task-notification> as an ordinary type:"user" message —
string body, no isMeta flag — so nothing filtered it and fact extraction saw the
harness's background-task plumbing (task id, tool-use id, status, summary) as
something the user said. Measured across 400 local transcripts: 39 such messages,
16,289 chars, every one of them the entire message.

Reproduced with the real reader on a real transcript (243 retained turns, 1 of
them a task-notification carrying 519 chars of transport), and verified after:
242 turns, 0 noise, every genuine turn preserved.

The tag joins MEMORY_TAG_RE, which already covered codex's <hook_prompt> for
exactly this reason. <system-reminder> joins it too: today those only ride inside
tool_result blocks, which this reader drops entirely, so it is insurance against
the harness moving them — the rule is tag-structural, not content-guessing.

Note what the issue asked for and this does NOT do. Its two named cases are
already handled here: skill bodies arrive with isMeta:true (8/8 in the sample)
and are dropped with every other meta line, and <system-reminder> is inside a
dropped tool_result. Only the third case was live.

Stripping removes the BLOCK and keeps surrounding text, so a message that is
nothing but a wrapper renders empty and is dropped as a no-content turn, while a
real message mentioning one keeps the user's words — the mistake the old plugin's
unanchored strip_channel_envelope made (#3124).
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.

claude-code: skill bodies and harness-injected blocks are retained as user conversation

1 participant