Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### New Features

- Two new hook entry points let a host agent tell CodeGraph which context is calling: `codegraph hooks pre-tool-use` tags each `codegraph_explore` call with the agent that made it, and `codegraph hooks post-compact` clears that context's already-sent record after a compaction. `codegraph install` wires them into Claude Code and Codex automatically — on Codex they are recorded as trusted as they are written, so it doesn't stop to ask you to review them — and already-sent tracking then follows each subagent and each compacted session instead of the connection they share. `codegraph uninstall` removes them, and hooks you added yourself are left alone either way. Other agents are unaffected — nothing changes for hosts that don't run them.

### Fixes

- `codegraph_explore` no longer tells a subagent that source was "already sent" when it went to a different agent. Harnesses like Claude Code route a subagent's tool calls over the same connection to CodeGraph as the main agent, so a fresh subagent could be handed a pointer to code its own context never received — and it would go read the file instead. Already-sent tracking is now kept per calling context.


## [1.6.0] - 2026-08-26

Expand Down
45 changes: 45 additions & 0 deletions __tests__/explore-cross-call-dedup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,51 @@ describe('a second call against a real index', () => {
expect(await explore(QUERY, b)).toBe(firstForA);
}, 180_000);

/**
* Per-caller bucketing (`sessionId`).
*
* One MCP connection is not one agent context: Claude Code subagents dispatch
* over the parent's connection, so an unbucketed record hands a subagent a
* pointer to source only the parent was ever sent — the exact "codegraph
* doesn't have it" shape that costs a Read. A host hook injects a distinct id
* per context; calls that carry none share the default bucket and behave
* exactly as they did before it existed.
*/
it('serves a second context in full — a subagent never got the first call', async () => {
const session = new ExploreSessionState();
const firstForA = await explore(QUERY, session, { sessionId: 'A' });
const firstForB = await explore(QUERY, session, { sessionId: 'B' });
expect(firstForB).toBe(firstForA);
expect(firstForB).not.toContain(POINTER);
}, 180_000);

it('still dedups the second call of ONE context', async () => {
const session = new ExploreSessionState();
await explore(QUERY, session, { sessionId: 'A' });
expect(await explore(QUERY, session, { sessionId: 'A' })).toContain(POINTER);
}, 180_000);

it('does not dedup an identified call against the unidentified bucket', async () => {
const session = new ExploreSessionState();
const unidentified = await explore(QUERY, session);
const identified = await explore(QUERY, session, { sessionId: 'A' });
expect(identified).toBe(unidentified);
expect(identified).not.toContain(POINTER);
}, 180_000);

it('clearSessionRecord drops one context\'s history and leaves its siblings', async () => {
const session = new ExploreSessionState();
const firstForA = await explore(QUERY, session, { sessionId: 'A' });
await explore(QUERY, session, { sessionId: 'B' });

expect(session.clearSessionRecord('A')).toBe(1);

// A was reset: its next call is a first call again, byte for byte.
expect(await explore(QUERY, session, { sessionId: 'A' })).toBe(firstForA);
// B never lost anything.
expect(await explore(QUERY, session, { sessionId: 'B' })).toContain(POINTER);
}, 240_000);

it('is off entirely under CODEGRAPH_EXPLORE_DEDUP=0', async () => {
const session = new ExploreSessionState();
const previous = process.env.CODEGRAPH_EXPLORE_DEDUP;
Expand Down
Loading