* 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>
* fix(directory): a schema-less codegraph.db does not make a project initialized (#1895)
isInitialized() accepted any existing .codegraph/codegraph.db, so an empty or
table-less file in an ancestor directory (an interrupted init, a stray touch,
a never-populated ~/.codegraph/) captured the upward walk of
findNearestCodeGraphRoot / resolveProjectPath / resolveServerRoot for every
project beneath it: the real index of a sub-project became unreachable, the
sub-project down-scan never ran, and commands either operated on the broken
file or failed opening it instead of showing the not-initialized guidance.
An initialized project is now one whose db carries the schema: the probe is
gated by file size (>= the 100-byte SQLite header), then the header magic,
then a read-only node:sqlite open with a single sqlite_master lookup for the
`nodes` table, memoized per path + mtime + size so hot callers (the prompt
hook, MCP root resolution on every call) never reopen an unchanged db. The
handle is closed immediately so the probe never holds the db on Windows; a
locked/busy db counts as live.
createDirectory() uses the same predicate, so `codegraph init` in the
directory with the broken file repairs it in place (the schema is applied
with CREATE ... IF NOT EXISTS) instead of refusing with "Already
initialized"; the CLI says what it found. Nothing is deleted automatically.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(directory): a database we cannot open read-only still counts as initialized
The schema probe failed CLOSED: a real WAL codegraph.db in a directory the
process cannot write (a read-only checkout, a mount, another user's tree)
made the read-only node:sqlite open throw — SQLite must create `-shm` to
open a WAL database — and isInitialized() reported the project as not
initialized, where main's file-exists check had been right.
The probe now fails OPEN. Once the size gate and the `SQLite format 3\0`
header have passed, the file is a SQLite database; only a SUCCESSFUL
sqlite_master query proving the `nodes` table absent may return false. Any
open/prepare error (locked, busy, cantopen, readonly, disk I/O) returns
true, the pre-existing behaviour for a database we cannot inspect. No error
strings are matched. The per path + mtime + size memo is unchanged, and
schema-less/empty files still return false, so `codegraph init` still
repairs them and never touches a real db.
Test: a WAL db copied into a `.codegraph/` made read-only with chmod stays
initialized — skipped on win32 and when running as root (root ignores mode
bits).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(init): refuse a codegraph.db that is not SQLite instead of promising a rebuild
Review found that a `.codegraph/codegraph.db` holding bytes that are not
SQLite at all counted as "schema-less", so `init` printed "rebuilding it"
and then failed with SQLite's "file is not a database". Only an empty file
or a SQLite database without the codegraph tables can be rebuilt in place;
`hasSchemalessDb` now means exactly that, and the new `hasForeignDbFile`
names the other case. `init` stops on it with exit 1, says which file it
is and to move or delete it, and never touches it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(directory): ask SQLite what codegraph.db is instead of reading its header ourselves (#1895)
The header check opened and closed a descriptor of our own on
`codegraph.db`. Closing any descriptor on a database file drops every
POSIX lock the process holds on it, including those of a connection it
already has open (sqlite.org/howtocorrupt.html §2.2.1) — and the MCP
server resolves projects through isInitialized on every call while it
holds the index as its writer. mcp-projectpath-lifecycle's takeover tests
caught it: after a real owner exits, the taking-over engine's next read
failed with "disk I/O error" (2 of 21 fail with the header read; 21/21
on main and with this change).
One read-only SQLite probe now answers all three questions: the schema
is there, SQLite opened it and the schema is not (repairable), or SQLite
refused it as not a database (SQLITE_NOTADB). Anything else — locked,
busy, no `-shm` possible — still fails open as initialized. SQLite's own
connections share one lock table per file, so opening and closing a
second one leaves the first one's locks alone. A test pins that
isInitialized and both init helpers never open the file themselves while
this process holds it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(changelog): keep the #1895 entry clear of #1917's, so the carries merge in any order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: danusha2345 <danusha2345@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Everyday uses of como and wie were treated as sufficient structural evidence for HIGH.
Remove these words from the unconditional keyword list so existing index verification determines HIGH, MEDIUM, or silence.
Add real-index CLI regressions while preserving existing English and multilingual structural tests.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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>
Reuse unsafeIndexRootReason before scanning indexed subprojects so stray manifests at home or broader roots cannot inject unrelated context. Preserve workspace adoption for #964.
Validation: four new regressions fail before the guard and pass after it; 48 relevant tests and npm run build pass. Confirmed the real os.homedir() leak before and after the fix with fixture cleanup.
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
Claude Code persists hook stdout over 10,000 characters to a file and shows
the model a 2 KB preview. The prompt-hook MAX of 16,000 always hit that path
once explore filled the budget. Cap at 9,000 (exported + unit-tested) so the
payload lands inline, with headroom for the wrapper and projectPath nudges.
Lands the approach from #1695 with a testable helper.
Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
The multilingual structural-question gate (#1134) matches stems as open
prefixes (left boundary only) so derived forms fire without enumeration.
Four English stems have common non-structural completions — callus,
calligraphy, Connecticut, connective, affectionate, Tracey — that
false-fired the HIGH (full-explore) tier. Those four now enumerate their
structural suffixes and re-assert the right boundary; callbacks/callable/
call sites are included so no structural form regresses. Also documents
the verified-unfixable Korean homograph class on the unsegmented table
(#1140): segmentation can't split 구조대 from 구조가, and a denylist would
break 구조대로.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(prompt-hook): fire the structural gate for Latin-script, Cyrillic, and JA/KO prompts (#1126)
The prompt-hook's keyword gate only knew English and simplified-Chinese
keywords, so a structural question in French (or Spanish, German, Italian,
Portuguese, Russian, Japanese, Korean, traditional Chinese) silently
no-op'd unless it happened to contain an identifier-shaped code token —
the #994 symptom, resurfaced for every other language.
Root causes fixed:
- JS \b is ASCII-only: a keyword whose first/last char is accented or
non-Latin (où, qué, Cyrillic, kana) can never match \bkeyword\b —
the same mechanism behind #994. Keyword matching now uses Unicode
lookaround boundaries ((?<![\p{L}\p{N}_]) … (?![\p{L}\p{N}_])).
- Bare-stem English entries never matched their own derived forms
(\barchitect\b can't match "architecture", \bdepend\b can't match
"dependencies"). Stems are now matched as word prefixes (leading
boundary only), which also lets one shared stem cover the Romance/
Germanic spellings that coincide.
- The "CJK" set was simplified-Chinese-only: Japanese (呼び出し, 仕組み,
実装 — and 追跡 ≠ 追踪), Korean, and traditional-Chinese terms are now
in the unsegmented substring set.
Code-token extraction and the graph-verification path are unchanged;
non-structural prose stays a zero-cost no-op in every language.
Fixes#1126
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(prompt-hook): extend the gate to tier-2 languages (VI/TR/ID/PL/UA/NL/CS/RO/HU/EL/Nordics/FI/HI/AR/FA/HE/TH)
The first pass covered the 10 largest languages; this closes the rest of
the major-developer-population set (~29 total). Notable per-language
mechanics the curation had to respect:
- Agglutinative languages (Turkish, Finnish, Hungarian) need stems, not
exact words — suffixes attach to everything (akışı, riippuu, működik).
- Indonesian me-/di-/ber- prefixes block leading-boundary stems, so
affixed forms are listed explicitly (memanggil, dipanggil, berfungsi).
- Arabic/Farsi/Hebrew are spaced but proclitics attach to the word
(وكيف = and-how), so they join the substring class with Thai.
- Ukrainian і/и spellings diverge from Russian (архітектур ≠ архитектур).
- Excluded terms that collide with English or code words: NL "pad",
SV "var", CS "tok", Catalan "com" (matches every .com domain) — with
regression tests pinning the exclusions.
Vietnamese was the sharpest gap: spaced Latin with heavy diacritics —
exactly the ASCII-\b failure class #1126 reports.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The UserPromptSubmit hook's structural-prompt gate was English-only, so a
structural question written in Chinese — or any non-Latin script — silently
injected nothing: JS `\b` is ASCII-only and never matches between Han
characters, so the keyword regex couldn't fire (and couldn't be extended in
place). To the user the hook looked unwired, with no error to explain why.
Make the gate language-aware, split into tested helpers in directory.ts:
- hasStructuralKeyword: English (\b-guarded) + CJK structural keywords.
- extractCodeTokens: identifier-shaped tokens (camelCase / snake_case /
name() / a.b) in any language — verified against the index via
getNodesByName before firing, so a tech brand like `JavaScript` that looks
like a symbol but isn't one here doesn't inject ~16KB of spurious context.
- isStructuralPrompt: the cheap candidate gate (keyword OR code-token).
Adds 21 unit tests for the gate (previously untested) covering the reporter's
verification table plus the false-positive guards.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MCP server gated tool availability on whether the server root had a
.codegraph/ index, so in a monorepo where only sub-projects are indexed the
agent saw zero tools — and couldn't reach an indexed sub-project even by
projectPath. A session started before `codegraph init` also never surfaced the
tools afterward. The Claude front-load hook had the mirror gap: it only walked
UP for an index, so it stayed silent at a monorepo root.
MCP server:
- Always expose the tool surface; when the root isn't indexed, send a
per-project instructions variant (pass projectPath) instead of the
"inactive" note. Safety comes from response SHAPE (success-shaped guidance,
never isError), not from hiding tools.
- Reword the no-default-project guidance to be per-project, not per-session,
and sharpen the projectPath schema description.
Front-load hook (UserPromptSubmit):
- Scan DOWN (bounded depth, workspace-root-gated) for indexed sub-projects and
shape the injection by topology: front-load the one the prompt names, nudge
about the rest, or list them when ambiguous.
Verified: full suite (1703 passed); a live two-package monorepo run confirms the
hook front-loads the correct sub-project with no cross-package leakage. The
front-load's net speed effect is the existing multi-file-vs-single-file
tradeoff, unchanged by this work.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>