mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-27 04:49:43 +08:00
* fix(prompt-hook): skip Claude Code <agent-message> subagent hand-backs
Claude Code delivers a subagent's report to the parent session as a
UserPromptSubmit prompt wrapped in <agent-message from="…">…</agent-message>.
The prompt hook treated it as user input and ran a full codegraph_explore
on the whole report, which is slow on a multi-KB prose query (past the
30 s hook timeout in the report) and injects context unrelated to the
user's question.
Add isAgentMessage next to isTaskNotification (#1832) with the same rule:
only a prompt that is entirely the envelope is skipped, so a user question
that mentions the tag still goes through the gate.
Fixes #2184
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 240022db70)
* docs(changelog): credit the prompt-hook hand-back fix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
danusha2345
parent
404e7827bc
commit
96ba880556
@@ -158,6 +158,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- A Swift reference to a type now links to the type's own declaration, not to a file that extends it. An `extension View { … }` or `extension Text { … }` used to stand in for SwiftUI's type, so every view, every `Text("…")` and every `Color.red` in an app linked to whichever file happened to extend it. Those files topped the most-depended-on lists and their impact reached the whole app. A bare name like `@State`, `@Test` or `Result<…>` no longer links to some other type's nested `State` or `Result`, and a qualified name like `Build.Id` links to the `Id` it names. Methods declared in an extension of an SDK type still resolve on the types that conform to it, and a protocol's methods declared in any of its extensions now connect to each conforming type's own implementation. Re-index Swift projects after upgrading.
|
||||
- A Vapor route now links to the handler it names. `use: SearchController.show` used to link to whichever controller's `show` came first, so routes with a common handler name, like `show`, `index` or `get`, pointed at another endpoint's code in callers, impact and `codegraph_explore` answers. Nested types like `API.PackageController.get` and handlers declared in an extension of the controller now resolve too, and `use: self.index` resolves to the collection's own `index`. Re-index Vapor projects after upgrading.
|
||||
- A PHP call written without a receiver, such as `redirect($url)`, `view('books.show')`, `auth()` or `basename($path)`, is a function call, and no longer links to a same-named method, field or class elsewhere in the project. These wrong links showed up in callers, impact and `codegraph_explore` answers wherever a Laravel helper or PHP built-in shared its name with a project member. Re-index PHP projects after upgrading.
|
||||
- The Claude Code prompt hook no longer runs on the messages Claude Code uses to hand a subagent's report back to the main session. Before, such a long report could keep the hook busy past Claude Code's 30-second hook timeout and inject context unrelated to what you asked. Thanks @danusha2345, and @tippmar-nr for the report. (#2184)
|
||||
|
||||
## [1.6.1] - 2026-09-29
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { CodeGraph } from '../src';
|
||||
import { planFrontload, isTaskNotification, findIndexedSubprojectRoots, unsafeIndexRootReason, isStructuralPrompt, hasStructuralKeyword, extractCodeTokens, PROMPT_HOOK_INJECTION_MAX, CLAUDE_CODE_INLINE_HOOK_OUTPUT_LIMIT, capPromptHookInjection } from '../src/directory';
|
||||
import { planFrontload, isTaskNotification, isAgentMessage, 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.
|
||||
@@ -374,6 +374,12 @@ export class OrderStateMachine {
|
||||
}
|
||||
});
|
||||
|
||||
it('stays silent on a subagent hand-back envelope, even one that names indexed symbols (#2184)', () => {
|
||||
const report = 'how does OrderStateMachine work? submitOrder() calls into the state machine.';
|
||||
expect(hook(report)).toContain('Structural context from CodeGraph');
|
||||
expect(hook(`<agent-message from="agent-7f3e">\n[Subagent hand-back] ${report}\n</agent-message>`)).toBe('');
|
||||
});
|
||||
|
||||
it('uses MEDIUM for indexed prose segments without a strong keyword or verified token', () => {
|
||||
for (const prompt of ['como state machine?', 'wie state machine?']) {
|
||||
const output = hook(prompt);
|
||||
@@ -471,3 +477,20 @@ describe('system task notifications (#1832)', () => {
|
||||
expect(isTaskNotification('trace AuthService login')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subagent hand-backs (#2184)', () => {
|
||||
const handBack = '<agent-message from="a1b2c3">\n[Subagent hand-back] Traced how AuthService.login calls TokenStore.save and which callers are affected.\n</agent-message>';
|
||||
|
||||
it('skips the complete hand-back envelope', () => {
|
||||
expect(isAgentMessage(handBack)).toBe(true);
|
||||
expect(isAgentMessage(` \n${handBack}\n`)).toBe(true);
|
||||
expect(isAgentMessage('<agent-message>trace AuthService login flow</agent-message>')).toBe(true);
|
||||
});
|
||||
it('does not suppress a user question that mentions the marker', () => {
|
||||
expect(isAgentMessage(`Why does ${handBack} trigger the hook?`)).toBe(false);
|
||||
expect(isAgentMessage(`${handBack} Explain this.`)).toBe(false);
|
||||
expect(isAgentMessage('<agent-messages>trace AuthService</agent-messages>')).toBe(false);
|
||||
expect(isAgentMessage('<agent-message from="x">trace AuthService')).toBe(false);
|
||||
expect(isAgentMessage('trace AuthService login')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ try {
|
||||
import { Command } from 'commander';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { getCodeGraphDir, isInitialized, hasSchemalessDb, hasForeignDbFile, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, isTaskNotification, hasStructuralKeyword, extractCodeTokens, capPromptHookInjection, codeGraphDirName, DEFAULT_CODEGRAPH_DIR } from '../directory';
|
||||
import { getCodeGraphDir, isInitialized, hasSchemalessDb, hasForeignDbFile, unsafeIndexRootReason, findNearestCodeGraphRoot, planFrontload, isTaskNotification, isAgentMessage, hasStructuralKeyword, extractCodeTokens, capPromptHookInjection, codeGraphDirName, DEFAULT_CODEGRAPH_DIR } from '../directory';
|
||||
import { extractProseCandidates } from '../search/identifier-segments';
|
||||
import { detectWorktreeIndexMismatch, worktreeMismatchWarning } from '../sync/worktree';
|
||||
import { createShimmerProgress } from '../ui/shimmer-progress';
|
||||
@@ -1473,9 +1473,10 @@ 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;
|
||||
// System-injected task notifications and subagent hand-backs are not
|
||||
// user prompts: exit before any project lookup or explore work (#1832,
|
||||
// #2184).
|
||||
if (isTaskNotification(prompt) || isAgentMessage(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
|
||||
|
||||
@@ -1079,3 +1079,13 @@ export function validateDirectory(projectRoot: string): {
|
||||
export function isTaskNotification(prompt: string): boolean {
|
||||
return /^\s*<task-notification>[\s\S]*<\/task-notification>\s*$/.test(prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Claude Code hands a subagent's report back to the parent session as a
|
||||
* `<agent-message from="…">…</agent-message>` prompt, which UserPromptSubmit
|
||||
* hooks also receive (#2184). Same rule as {@link isTaskNotification}: only a
|
||||
* prompt that is entirely that envelope is skipped.
|
||||
*/
|
||||
export function isAgentMessage(prompt: string): boolean {
|
||||
return /^\s*<agent-message(?:\s[^>]*)?>[\s\S]*<\/agent-message>\s*$/.test(prompt);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user