mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
Claude Code's UserPromptSubmit hook also fires for the `<task-notification>…</task-notification>` blocks the host injects as `user` messages when a background agent finishes. Those blocks are long machine text full of identifier-shaped tokens, so they pass the structural gate and the whole blob is sent to codegraph_explore as the query — a 3.5s median (7.4s max) stall on the blocking path, on text the user never typed. Recognize a prompt that consists solely of one task-notification envelope (leading/trailing whitespace allowed) and exit before any project lookup, gate or explore work. A prompt that merely mentions the marker, or has text around it, is still treated as a user prompt. Fixes #1832 Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -145,6 +145,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### Fixes
|
||||
|
||||
- The Claude Code prompt hook no longer runs on the task-notification messages Claude Code injects when a background agent finishes, removing a multi-second stall on every such turn. (#1832)
|
||||
|
||||
- Rust calls on `self` now stay with the enclosing type instead of linking to an unrelated type’s same-named method. Thanks @L4XB. (#1861)
|
||||
|
||||
- Turning telemetry off now resets its identity and stops running processes from recording, sending, or restoring unsent data. (#1869)
|
||||
|
||||
@@ -12,7 +12,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { planFrontload, findIndexedSubprojectRoots, unsafeIndexRootReason, isStructuralPrompt, hasStructuralKeyword, extractCodeTokens, PROMPT_HOOK_INJECTION_MAX, CLAUDE_CODE_INLINE_HOOK_OUTPUT_LIMIT, capPromptHookInjection } from '../src/directory';
|
||||
import { planFrontload, isTaskNotification, findIndexedSubprojectRoots, unsafeIndexRootReason, isStructuralPrompt, hasStructuralKeyword, extractCodeTokens, PROMPT_HOOK_INJECTION_MAX, CLAUDE_CODE_INLINE_HOOK_OUTPUT_LIMIT, capPromptHookInjection } from '../src/directory';
|
||||
|
||||
// Make the built-in exports configurable so HOME can point at a real temp
|
||||
// fixture without changing the process environment or the user's home files.
|
||||
@@ -376,3 +376,16 @@ describe('prompt-hook injection cap (#1694)', () => {
|
||||
expect(out.length).toBeLessThan(CLAUDE_CODE_INLINE_HOOK_OUTPUT_LIMIT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('system task notifications (#1832)', () => {
|
||||
it('skips the complete system envelope', () => {
|
||||
expect(isTaskNotification('<task-notification>trace AuthService login flow</task-notification>')).toBe(true);
|
||||
expect(isTaskNotification(' <task-notification>\n<task-id>abc</task-id>\n<summary>done</summary>\n</task-notification>\n')).toBe(true);
|
||||
});
|
||||
it('does not suppress a user question that mentions the marker', () => {
|
||||
expect(isTaskNotification('Why does <task-notification>trace</task-notification> trigger the hook?')).toBe(false);
|
||||
expect(isTaskNotification('<task-notification>trace</task-notification> Explain this.')).toBe(false);
|
||||
expect(isTaskNotification('<other-tag>trace AuthService</other-tag>')).toBe(false);
|
||||
expect(isTaskNotification('trace AuthService login')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ try {
|
||||
import { Command } from 'commander';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { getCodeGraphDir, isInitialized, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, hasStructuralKeyword, extractCodeTokens, capPromptHookInjection } from '../directory';
|
||||
import { getCodeGraphDir, isInitialized, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, isTaskNotification, hasStructuralKeyword, extractCodeTokens, capPromptHookInjection } from '../directory';
|
||||
import { extractProseCandidates } from '../search/identifier-segments';
|
||||
import { detectWorktreeIndexMismatch, worktreeMismatchWarning } from '../sync/worktree';
|
||||
import { createShimmerProgress } from '../ui/shimmer-progress';
|
||||
@@ -1413,6 +1413,9 @@ program
|
||||
let input: { prompt?: string; cwd?: string } = {};
|
||||
try { input = JSON.parse(raw); } catch { return; }
|
||||
const prompt = String(input.prompt || '');
|
||||
// System-injected task notifications are not user prompts: exit before
|
||||
// any project lookup or explore work (#1832).
|
||||
if (isTaskNotification(prompt)) return;
|
||||
|
||||
// Gate telemetry: how often each tier fires vs. no-ops — counter names
|
||||
// only, NEVER prompt content (see TELEMETRY.md). This is the data that
|
||||
|
||||
@@ -899,3 +899,14 @@ export function validateDirectory(projectRoot: string): {
|
||||
errors,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Claude Code injects `<task-notification>…</task-notification>` blocks as
|
||||
* `user` messages when a background agent finishes, and UserPromptSubmit
|
||||
* hooks receive them exactly like typed prompts (#1832). The whole prompt
|
||||
* must be that single envelope; a user question that merely mentions the
|
||||
* marker is still a prompt.
|
||||
*/
|
||||
export function isTaskNotification(prompt: string): boolean {
|
||||
return /^\s*<task-notification>[\s\S]*<\/task-notification>\s*$/.test(prompt);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user