Repository navigation
fix(sync): an import links the file it names when that file appears later - #2392
Merged
Merged
Conversation
…ater A sync retried parked failed refs by name_tail (#1240), but an import names a file, a folder or a namespace, not a symbol, so the lookup never found one: the tail of `package:app/b.dart` was `dart`, of `inc/db.php` `php`, of `./req` `/req`. Most languages never parked theirs at all: an import that named no file reached the importing file's own `import` node through matchByQualifiedName (its qualified name is the module path) and resolved there, so `./x` in TS/JS, `import pkg.mod`, `#include "a/b.h"`, `<stdio.h>`, a C# `using` of an outside namespace, Go module paths and R, Solidity, Svelte and Vue imports were consumed by an edge to themselves. A Lua `require` bound the local it is assigned to. The import linked only once the importer changed, or on a full index. - resolveOne's target gate drops an `imports` ref's own import statement, so the ref parks as failed (#2388 did this for Dart alone). - A Lua / Luau `require` resolves to a module file or nothing. - An import written as a path is parked under its last segment's stem (`package:app/b.dart` -> `b`, `./z.js` -> `z`): src/db/reference-tail.ts. - Sync retries the failed imports an ADDED file can satisfy: by its name, stem and folder (importPathKeys), as a failed import's tail or whole name, and by the changed files' namespace / module names as a whole name (C# `using Foo.Bar`, Java `import com.z.*`). Same per-key ceiling. - Schema v12: two partial indexes over failed imports, which the planner needs to avoid reading every failed call that shares a tail; the migration rewrites the tails of path imports parked by an older version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 6, 2026
Merged
colbymchenry
added a commit
that referenced
this pull request
Oct 7, 2026
…ppears later (#2422) A router names the page or layout it loads lazily by the module's path: React Router's `lazy-import:./pages/Team`, Vue Router's and Angular's `import:./home/home.component#HomeComponent`, each also behind `layout:`. Sync retries a parked failed ref by name_tail (#1240), and these were parked under a fragment of the path ('/pages/Team', 'component#HomeComponent') that no file's keys match. So a route whose module was added after the router was indexed, or whose module gained its component in a later edit, stayed unlinked until the router file changed or the project was indexed again. #2392 fixed the same gap for imports, #2403 for Liquid's path references. - referenceNameTail parks a module reference under the stem of its path behind 'module:' ('module:Team'), a key no symbol's name can be, so name lookups never retry it by a namesake. - Sync adds the changed files' moduleReferenceKeys (importPathKeys behind 'module:') to the symbol retry: an added file can be the module, and an edit can give the module the component the route renders. - Schema v14 rewrites the tails parked before. Its select keeps SQLite off idx_unresolved_status with `+status`, reading idx_unresolved_name ranges instead of every failed row (vscode's index: about 1 s down to ~12 ms). - The retry resolves in row order, as a full index does, so when a route's module reference and its layout reference make the same edge, sync keeps the one a fresh index keeps (ghostfolio's markets page). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 11, 2026
fix(sync): a Python import of a name a package re-exports links when the package appears later
#2507
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
codegraph sync(and the watcher) now links an import to the file it names when that file is added after the importer was indexed, or comes back after being deleted. Before, the import stayed unlinked until the importing file itself changed, or a full index ran. This is a pre-existing gap: #2388 changed which edge a Dart import got, not whether sync retries it.It never relinked, for two reasons:
The retry key never matched. A failed ref is parked under
name_tail, its last./:segment (Incremental sync misses cross-file relations after a new export is added #1240), and a sync retries the failed refs whose tail is a node name in the changed files. For a path import that tail was the extension or a path fragment, and no node is ever named that:package:app/b.dart→dartinc/db.php→phplib/foo.rb→rbb.hrl→hrlrequire('./req')→/reqMost languages never parked the import at all. An import that names no file reached the importing file's own
importnode throughmatchByQualifiedName, since that node's qualified name is the module path, and resolved there at 0.95. The ref was consumed, so nothing ever retried it. This covered:./ximport pkg.mod#include "a/b.h"and<stdio.h>require("lib.mod")source("R/x.R")usingof an outside namespacefix(dart): an import or export links the file its URI names, not a namesake #2388 removed this for Dart only. A Lua/Luau
requirealso bound by exact name to the local it is assigned to (local lfs = require "lfs"→variable lfs).How:
gateTargetKind(theresolveOneseam): animportsref never resolves to animportnode in its own file, so it parks as failed instead. The C# branch ofmatchByQualifiedNamestill returns the ownusingto end the lookup there, and the gate drops it.Lua/Luau
requirejoins the no-fallthrough guard (PHP includes, COBOL, Nix, Dart, Terraform): it resolves to a module file or nothing.Path-aware tail (
src/db/reference-tail.ts, moved out of queries.ts so the migration shares it). Animportsref with a/in it is parked under the last segment's stem:package:app/b.dart→b./foo,./foo.js→fooinc/db.php→dbexample.com/app/pkg→pkgNames without a
/keep their old tail (pkg.mod→mod,com.z→z,App\Models::User→User), so the existing symbol retry of binding refs is unchanged.A second sync lookup,
getRetryableFailedImports, runs after the existing symbol-name retry (results deduped by row id):importPathKeys): its name and stem, and its folder's name and stem.lib/src/d.dartgivessrc,d.dartandd. The folder key covers./ui→ui/index.ts,pkg→pkg/__init__.pyorpkg/init.lua, and Go packages. A key matches a failed import's tail or its whole name; the whole-name match covers bare#include "b.h",require 'db.php'and Dartpart 'a.g.dart'. Modified files add no path keys, because they already existed.namespace/modulenode names, matched against a failed import's whole name when its tail differs: C#using Foo.Bar→ the namespace nodeFoo.Bar, and Javaimport com.z.*. All node names would be wrong here:getNodeNamesByFilesincludes the file's own import nodes (compress/gzip,@excalidraw/common), and that re-selected every other file's import of the same module. That cost up to 2,643 refs per added file on prometheus before I narrowed it.Schema v12: two partial indexes over failed imports,
(reference_kind, name_tail)and(reference_kind, reference_name).index,types). The planner picksidx_unresolved_failed_tail/idx_unresolved_name:sqlite_stat1averages 3 rows per tail across 828k failed refs, but common tails are heavily skewed, and every matching row is read just to checkreference_kind.reference_kind, the planner chooses them with or without stats: under 0.2 ms per query and 0.77 ms per added file.dart→b), so those retry without a re-index.Confirming the shapes in the issue
package:app/b.dartdart. Confirmed../foo/fooonly for a CommonJSrequire('./foo'). ESMimport … from './foo'never parks: it binds to its own import node.a/b.h#include "c.h"did relink on main, through the CG-33 rebind, because the own import node is namedc.hlike the new file.a/b.hnever did.inc/db.phpphp. Confirmed.rb), Erlang (hrl), Nix (nix//bar), COBOLCOPY X(tail fine, but no node is namedX), Python (own node, or a binding tail no node has), Lua, Luau, R, Solidity, ObjC, Svelte, Vue, Javaimport com.z.*.use(symbol tails); C# (CG-33 rebind of the ownusing). Go package imports never link a file in either arm.module.<name>:file: the ref carries no path (the resolver readssource), so no key can reach it.Validation
Main @837a1866 vs this change, indexed on the wasm path. Rebased onto #2391, which only adds Dart
partrefs; those go through the no-fallthrough Dart branch and never self-bind. The per-language probe and the new tests give identical results with the kernel loaded and withCODEGRAPH_KERNEL=0.require→ its own localRoact,TestEZ)unresolvedNameexclusion count moves by at most 2.codegraph affectedare unchanged on a full index.Retry volume. This counts the extra failed refs the new lookup selects per added file, beyond what the existing symbol retry already selects. Each indexed file is treated in turn as the one a sync just added, using the fix-arm index:
The other option, retrying every failed import whenever a sync adds files, would cost 10–1,000× more per add. It also needs the path tails anyway: with the old tails every Dart import shares the key
dartand every C includeh, so each such group exceeds the ceiling and is skipped whole.End to end on vscode. On copies of both arms' indexes I deleted
chatService.ts(1,549 incomingimportsedges from 399 files), rancodegraph sync, restored the file, and rancodegraph syncagain:importsedges main loses.imports+ 165references+ 2implementsedges, all toIChatService. 517 failed refs share that tail, which is over the existing per-name ceiling. Unchanged by this PR.Tests
__tests__/sync-import-retry.test.ts(27 tests):part 'a.g.dart'written after the library was indexed), TypeScript, JavaScript (ESM + CommonJS), C, C++, Objective-C, PHP, Ruby, Python, Lua, Luau, Nix, R, Solidity, Erlang, COBOL, Java package wildcard, C#using, Svelte and Vue;sync()with the importer unchanged, and the result equal to a full re-index;importPathKeysshapes, the lookup's matching and ceiling, the v12 migration (tail rewrite + replay), and anEXPLAIN QUERY PLANguard that the lookups use the new indexes.tsc --noEmitis clean.git-index-currencyandsync, while another job loaded the box. Both files pass alone (71/71).git-index-currencytimeouts and a WindowsEBUSYteardown indaemon-pid-reuse), and those files pass alone (14/14).uiproject passes (18/18).Not covered
module.<name>:file) carry no path, so the retry can't key them.EXTRACTION_VERSION: none of fix(dart): link a chained call only through its receiver's declared types (#750) #2376–fix(dart): a library links the files its part directives name #2391 did, and whether this warrants the re-index hint is your call.pubspec.yaml, tsconfig paths,go.mod,package.jsonworkspaces) doesn't trigger the retry, because those aren't the added source file. A workspace package (@scope/pkg→packages/pkg/src/index.ts) only matches when its entry sits in a folder named for the package.🤖 Generated with Claude Code