Skip to content

fix(sync): an import a barrel passes on under another name relinks when its module gains it - #2665

Merged
colbymchenry merged 1 commit into
mainfrom
claude/barrel-reexport-sync
Oct 19, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/barrel-reexport-sync

Conversation

@colbymchenry

@colbymchenry colbymchenry commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

codegraph sync and the file watcher now relink an import that reaches a module through a barrel passing it on under another name, once an edit gives the module what is imported. matx's app/components/index.js says export { default as MatxLoading } from './MatxLoading', and MatxSuspense.jsx imports { MatxLoading } from that barrel. That was the one edge #2649's sync check left behind (its "Follow-up" section).

Before, after an edit gave MatxLoading.jsx its export default function Loading, the import and the calls through it stayed failed. If a name match had taken the name somewhere else while the module lacked it, the edge stayed there instead. In vben, alert stayed on the alert.vue component, though the alert barrel passes it on from vbenAlert. Only a change to the importer or a fresh index fixed it.

Cause

An edit's sync re-opens two things:

  • the failed references waiting under a name the changed file declares;
  • through CG-33's rebind, the edges into names that changed.

An importer below a renamed re-export waits under neither: MatxLoading and alert are names the changed files never declare. reopenReferencesThroughBarrels walked the barrels, but only to retry what waits under their module keys, which is the lazy-route case from #2560. An added file was already covered: reopenReferencesThroughAddedFiles re-opens its importers' bindings through barrels, which is why only edits failed.

Fix (src/resolution/module-importers.ts)

  • reopenReferencesThroughBarrels records each importer below a barrel with the names the barrel passes the changed files on as. It then re-opens:
    • the importer's failed references through those names (getFailedReferencesThrough);
    • the importer's edges through the names the changed files never declare, bound before this sync resolved anything (reopenEdges up to edgesBeforeResolution), as the added-file path does.
  • A name the files do declare stays with the existing retry and CG-33's rebind, which cap a common name. So a plain export { Button } from './Button' doesn't re-open every importer's Button edges on each edit.
  • The per-importer lookup replaces a repo-wide retry by the passed-on names, whose per-name ceiling (500) skips a name as common as alert. The barrels' module keys are still retried for lazy routes.
  • passedOn is shared with the added-file path. It now also reads an import a barrel exports again, with the names it is exported as (import Card from './Card'; export { Card as AppCard }). Before, only the lazy-route walk followed that form. ImportReader.exportedBindings becomes exportedAs, which maps each local to the names it is exported under.

Validation

The two arms are engine builds of main 0d33925a and this branch. The change is sync-only, so I checked that the fresh indexes are byte-identical on all 10 repos (scripts/dump-graph.mjs).

Sync convergence. For each repo, 20 files were picked from the main arm's fresh index in two ways:

  • renamed: files a barrel re-exports under another name, leaving out any that import from that barrel themselves;
  • barrel: any file a barrel imports.

Each pick ran two scenarios: indexed without the files then added back (add), and indexed with a stub in their place then edited back (edit). After codegraph sync, each graph was compared with its own arm's fresh index. The table counts differing lines in the edges and unresolved-refs sections, main → this branch:

repo renamed: add renamed: edit barrel: add barrel: edit
uilibrary/matx-react 0 → 0 2 → 0 0 → 0 0 → 0
vbenjs/vue-vben-admin 29 → 29 ¹ 42 → 28 ¹ 30 → 30 ¹ 30 → 30 ¹
SigNoz/signoz 0 → 0 12 → 0 0 → 0 0 → 0
alan2207/bulletproof-react 0 → 0 (2 files) 0 → 0 0 → 0 0 → 0
codedthemes berry, mantis; minimal-ui-kit/material-kit-react; excalidraw no renamed re-exports 0 → 0 0 → 0
bradtraversy/proshop_mern no renamed re-exports 0 → 0 (3 files) 0 → 0
leerob/next-saas-starter no barrels

¹ What's left on both arms is only heuristic jsx-render / vue-handler edges from the restored .vue components; see "Not in this PR".

Where the edit lines went:

  • matx: MatxSuspense.jsx's MatxLoading import was failed and is now linked to MatxLoading.jsx's Loading (fix(js,ts): an import written from a jsconfig or tsconfig baseUrl links the file it names #2649's leftover).
  • vben: in alert-builder.test.ts, the alert import and its 4 calls move from the alert.vue component (exact-match 0.5) to vbenAlert (import 0.9). confirm's import and call were failed and now link vbenConfirm.
  • signoz: { RichCodeBlock } from '../blocks', re-exported as default as RichCodeBlock, had bound by name to the same function while the module was a stub. Its 6 edges in MessageBubble.tsx and StreamingMessage.tsx are back to import, as in a fresh index.

Cost. I timed the edit scenario's codegraph sync on both arms, interleaved, using sync-barrel-modules from CODEGRAPH_SYNTH_TIMINGS=1. The barrel phase grows by a few milliseconds, and sync wall time doesn't move beyond run-to-run noise:

case barrel phase, main → branch refs re-opened sync wall median, main → branch
vben, renamed (5 runs) 3–4 → 5–6 ms 3 → 10 4.39 → 4.49 s
vben, barrel (3 runs) 6–10 → 7–9 ms 1 → 1 4.62 → 4.34 s
excalidraw, barrel (3 runs) 145–155 → 153–166 ms 4 → 17 5.17 → 5.25 s
signoz, renamed (3 runs) 12–16 → 17–22 ms 4 → 10 15.9 → 15.6 s

Excalidraw's ~150 ms is main's existing walk. Its convergence row is unchanged, so its extra re-opened references resolve as they did before. Edges through names the changed files declare stay with CG-33's capped rebind. That keeps a plain export { Button } from './Button' from re-resolving every importer's Button on each edit.

Tests. I ran the full suite with --maxWorkers=4 --minWorkers=1 on Windows. On the rebased head 7cb48b4b (main 90d6501b): 7,042 passed, 407 skipped, and 1 failure. That failure is oversize-file-not-read's "400 MB sparse fixture" check, which hit its 5 s limit under full-suite load and passes alone (2 of 2, under 700 ms). Before the rebase, on 0d33925a, it was 7,027 passed, 0 failed.

Tests

__tests__/sync-barrel-renamed-reexport.test.ts has a matx-shaped fixture: a barrel with default as MatxLoading, Spinner as AppSpinner and an unrenamed Card. It runs four scenarios, each compared with a fresh index of the same files:

scenario before this PR with it
edit gives the modules their exports fails (MatxLoading, AppSpinner stay failed) passes
modules added after their importers passes passes
edit, with namesakes elsewhere that a name match takes first fails (the calls stay on src/legacy/*) passes
added, with namesakes passes passes

Each half of the fix is load-bearing:

  • dropping the edge re-open fails only the namesake edit test;
  • dropping the per-importer failed-ref lookup fails only the first edit test.

The existing barrel and sync suites pass: sync-nested-barrel-route, sync-module-importers, sync-import-*, sync-route-module-retry, sync-lazy-route-value, sync-jsx-import-forwarding, sync-python-import-binding-retry, 108 tests in all.

Not in this PR

  • vben's synthesized Vue edges. After an add or edit sync of vben's .vue files, 28-30 jsx-render / vue-handler edges from those components are missing. One example is alert.vue's @click="handleCancel" handler in its own script. The loss is identical on main and on this branch, in every scenario, and has nothing to do with barrels. A two-file Vue project converges, so the cause is specific to the larger repo. It's filed as a separate task.
  • An import a barrel exports again under another name (import Card from './Card'; export { Card as AppCard }). passedOn now walks it, but a fresh index doesn't link import { AppCard } through it either. That's a resolver gap, which open fix(react): a JSX tag renders what the file imports under that name #2451's clauseImports covers. Until then, the retry re-opens those references and they fail again, as in a fresh index.
  • Changed files that aren't ES modules. The walk still starts only from them, so a .vue file a barrel passes on under another name (export { default as VbenButton } from './button.vue') seeds nothing. Its component node exists whatever the file holds, so that import binds in both states.

🤖 Generated with Claude Code

…en its module gains it

A sync that gives a module a definition retries the failed references
waiting for the names the module declares, and rebinds the edges into the
names that changed (CG-33). An importer that reaches the module through a
barrel passing it on under another name waits under neither: matx's
`export { default as MatxLoading } from './MatxLoading'` hands
MatxLoading.jsx to `import { MatxLoading } from 'app/components'`, a name
the file never declares. That import and the calls through it stayed
failed, and where a name match had taken the name elsewhere while the
module lacked it (vben's `alert`, which its alert barrel passes on from
`vbenAlert`, onto the `alert.vue` component), the edge stayed there, until
the importer changed or the project was indexed again.

reopenReferencesThroughBarrels now records each importer below a barrel
with the names the barrel passes the changed files on as, and re-opens that
importer's failed references through those names and, for the names the
files never declare, its edges bound before the sync resolved anything, as
the added-file path does. A declared name stays the retry's and the
rebind's, which cap a common one. The per-importer lookup replaces the
repo-wide retry by the passed-on names, whose per-name ceiling skips a name
as common as `alert`; the barrels' module keys are still retried for lazy
routes. passedOn, which the added-file path shares, now also reads an
import a barrel exports again, with the names it exports it as (`import
Card from './Card'; export { Card as AppCard }`), which only the lazy-route
walk followed before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant