diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d38859d8..392e10bb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -161,6 +161,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - 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) - Newer MCP clients such as Antigravity 2.5 connect again: the server now answers their `server/discover` probe right away with "method not found", so they go straight on to the regular handshake instead of waiting on a reply that could take seconds or never come. Thanks @danusha2345, and @samem26 for the report. (#2084) - A CodeGraph session that queried another project through `projectPath` no longer keeps that project locked for as long as it runs: after 10 minutes without a query it lets the project go, so the project's own session and `codegraph index` can take over again (tune with `CODEGRAPH_PROJECT_IDLE_TIMEOUT_MS`, `0` keeps it open). Thanks @danusha2345, and @bompus for the report. (#2087) +- Claude Code now shows the model the full text of `codegraph_status` and of explore's stale-index refusal. Both results also carried `structuredContent`, and Claude Code shows a result's `structuredContent` in place of its text, so the model saw only a freshness JSON object: no refusal message, no retry guidance and no status report. Both results are now text only. Thanks @bompus. (#2088) ## [1.6.1] - 2026-09-29 diff --git a/__tests__/mcp-stale-refusal.test.ts b/__tests__/mcp-stale-refusal.test.ts index 53362c7a2..309cf92ee 100644 --- a/__tests__/mcp-stale-refusal.test.ts +++ b/__tests__/mcp-stale-refusal.test.ts @@ -121,7 +121,9 @@ describe('a degraded index refuses answers from changed files (#1959)', () => { const result = await handler.execute('codegraph_search', { query: 'unicodeSymbol' }); expect(result.isError).toBeFalsy(); expect(result.content[0].text).toContain('cannot answer from this index'); - expect(result.structuredContent).toEqual({ freshness: { stale: [relative], unchecked: [] } }); + expect(result.content[0].text).toContain(`- ${relative}`); + // Claude Code would show structuredContent in place of this text (#2088). + expect(result).not.toHaveProperty('structuredContent'); expect(result).not.toHaveProperty('_cgAnswerFiles'); }); @@ -137,7 +139,7 @@ describe('a degraded index refuses answers from changed files (#1959)', () => { ); it('refuses rather than silently validating only the first 200 contributing files', async () => { - for (let i = 0; i < 201; i++) { + for (let i = 0; i < 230; i++) { fs.writeFileSync(path.join(root, `caller${i}.ts`), `import { alphaOnly } from './alpha'; export function caller${i}() { return alphaOnly(); }\n`); } @@ -147,7 +149,13 @@ describe('a degraded index refuses answers from changed files (#1959)', () => { const result = await handler.execute('codegraph_impact', { symbol: 'alphaOnly' }); expect(result.isError).toBeFalsy(); expect(result.content[0].text).toContain('validation budget'); - expect((result.structuredContent!.freshness as { unchecked: string[] }).unchecked.length).toBeGreaterThan(0); + const text = result.content[0].text; + const total = Number(text.match(/validation budget for (\d+) files/)![1]); + // A long unchecked list stays bounded: the first 20 and a count of the rest. + expect(total).toBeGreaterThan(20); + expect(text.split('\n').filter(line => /^- (?!…)/.test(line))).toHaveLength(20); + expect(text).toContain(`- … ${total - 20} more (narrow the query)`); + expect(result).not.toHaveProperty('structuredContent'); expect(result.content[0].text).not.toContain('**Impact'); }); diff --git a/__tests__/mcp-status-freshness.test.ts b/__tests__/mcp-status-freshness.test.ts index 19c14f7ce..923f77ae3 100644 --- a/__tests__/mcp-status-freshness.test.ts +++ b/__tests__/mcp-status-freshness.test.ts @@ -42,9 +42,8 @@ describe('MCP status freshness (#1959)', () => { fs.writeFileSync(path.join(root, 'add.ts'), 'export const added = 1;\n'); const result = await handler.execute('codegraph_status', {}); - expect(result.structuredContent).toEqual({ freshness: { - lastIndexedAt: cg.getLastIndexedAt(), changes: { added: 1, modified: 1, removed: 1 }, complete: true, - } }); + // Claude Code would show structuredContent in place of this text (#2088). + expect(result).not.toHaveProperty('structuredContent'); const changed = result.content[0].text; expect(changed).toContain('**Changes since index:** 1 added, 1 modified, 1 removed'); }); diff --git a/src/mcp/tools.ts b/src/mcp/tools.ts index d11fa40c5..3aa5fab87 100644 --- a/src/mcp/tools.ts +++ b/src/mcp/tools.ts @@ -1538,7 +1538,6 @@ export interface ToolResult { _cgExploreEmission?: ExploreEmission; /** Internal structured provenance, preserved by query workers and stripped by execute. */ _cgAnswerFiles?: AnswerFile[]; - structuredContent?: Record; } /** @@ -2811,7 +2810,10 @@ export class ToolHandler { if (validation.unchecked.length > 20) lines.push(`- … ${validation.unchecked.length - 20} more (narrow the query)`); } lines.push('Retry after a successful codegraph sync, or narrow the query.'); - return { ...this.textResult(lines.join('\n')), structuredContent: { freshness: validation } }; + // Text only: Claude Code shows the model a result's structuredContent + // in place of its text (#2088). The text names every stale file and + // the first unchecked ones; the rest only need a narrower query. + return this.textResult(lines.join('\n')); } } // Record + STRIP before anything else touches the result: the emission is @@ -8060,9 +8062,8 @@ export class ToolHandler { } } - return { ...this.textResult(lines.join('\n')), structuredContent: { - freshness: { lastIndexedAt, changes, complete: changes !== null }, - } }; + // Text only, for the same reason as the stale-answer path (#2088). + return this.textResult(lines.join('\n')); } /**