Repository navigation
test(cross-version): load releases that import a package main dropped - #25731
Conversation
…n dropped The newest release (v1.4.221) imports @streamparser/json, which main removed. The harness runs release source against the current install, and the release's RPC dispatcher imports its whole method table, so every wire suite failed at import time. Extraction now gives each package the release declares and the current tree no longer declares a stand-in in the checkout's own node_modules that loads but throws, naming the package, on any use. Co-Authored-By: Claude <noreply@anthropic.com>
…fest diff A package gets a stand-in only when release source imports it at runtime, the current tree does not declare it, and it does not resolve from the checkout. A package still reachable from the checkout keeps the real install, and one the current tree declares but cannot resolve stays a loud failure. Type-only imports, comment prose and scripts in strings are ignored. Co-Authored-By: Claude <noreply@anthropic.com>
📝 WalkthroughWalkthroughRelease checkout preparation now collects package imports and installs stand-ins for eligible packages that are unavailable in the checkout. The change updates renderer-alias rewriting to return rewritten source text and increments the checkout format. Tests cover package import handling, stand-in behavior, and loading a historical release with an unavailable package. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to This only changes the cross-version test harness. A narrow edge case yields a less clear error message but still fails, so the merge risk is low. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change affects compatibility testing rather than shipped application behavior. Declared dependencies remain protected from replacement, and checkout recovery is preserved. Handling of every older-release import pattern has not been established. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/e2e/cross-version-wire/release-missing-packages.ts (1)
17-19: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueDetect value-level re-exports with a named clause correctly, and cover bare
export * from.
STATIC_IMPORTrequiresfromafter a clause made only of[\w$*{},\s]. This matchesexport { x } from 'mod'andexport * from 'mod'. It does not matchexport * as ns from 'mod', because the*branch inimportedNamesis skipped, but the regex itself does match. The gap is elsewhere.export * from 'mod'records the package with an empty name set, so the stand-in has no named exports. A release that re-exports names throughexport { x } from 'mod'works, but a consumer that imports a name throughexport *fails at link time with a missing-export error, not the clear stand-in message.This is an edge case with a limited effect on the harness. Record this limitation in a comment, or add a test for it.
Source: Learnings
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
91912716-0247-466e-b79b-5f57e176f430
📒 Files selected for processing (5)
tests/e2e/cross-version-wire/release-checkout-tree.tstests/e2e/cross-version-wire/release-checkout.tstests/e2e/cross-version-wire/release-checkout.unit.test.tstests/e2e/cross-version-wire/release-missing-packages.tstests/e2e/cross-version-wire/release-missing-packages.unit.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.
ELI5
Every PR's "cross-version wire compatibility" CI job has been red since 22:53 UTC on 2026-10-05, and no assertion is failing. The harness simply can't load the newest release's code anymore.
The harness checks that today's code still talks to the last shipped release. It does that by running the release's source files against today's installed packages. The newest release, v1.4.221, uses a JSON parsing package (
@streamparser/json) that main removed in #25202. So three test files die at import withCannot find package '@streamparser/json'(for example, run 37364132538, attempt 3, on #25624).This PR lets the harness load that release code again. Using the removed package still fails, with a clear message.
What Changed
What changes for users: nothing. This only touches the test harness, and it unblocks CI on every open PR.
Why the release reaches that package: the harness imports the release's RPC dispatcher, and that file statically imports the release's whole method table. That pulls in about 3,800 release files, including the session scanner in
ai-vault/session-document-stream.ts, which imports@streamparser/json. The three failing files arecross-version-agent-session-send,cross-version-agent-session-wireandcross-version-terminal-wire.New mechanism: while extracting a release, the harness already reads every source file to rewrite renderer aliases. In the same pass it now records each package the release imports at runtime and the names it imports. Type-only imports, comment text, and scripts embedded in strings are skipped.
A package gets a stand-in only if all of these hold: the release imports it at runtime, the current
package.jsondoes not declare it, and it does not resolve from the checkout (Node's lookup, walking upnode_modulesfrom the checkout, which sits inside the repository).The stand-in goes into the extracted checkout's own
node_modulesand exports the same names, so the release loads. Any real use (calling, constructing, or reading a property) throws:release <commit> imports '<package>', which the current tree does not install, so this release code path cannot run here.A package still reachable from the checkout keeps the real install, even when the current tree no longer declares it. A package the current tree declares but cannot resolve is a broken install, and it still fails loudly at import.
The bound: if a wire suite actually uses a stand-in package, it fails with that package named. So a real wire dependency can't be silently hidden; only release code the suites never run gets past the missing package.
On the real trees this gives exactly
@streamparser/jsonfor v1.4.221 and v1.4.211, andpslplus@electron-toolkit/preloadfor v1.4.190.CHECKOUT_FORMATgoes from 4 to 5, so cached release trees are rebuilt with the stand-ins.Why
Cutting the import edge isn't possible: it lives inside the frozen release source (
rpc/dispatcher.ts→methods/index.ts), not in harness code. The agent-session suites also need the release's real method table on purpose.Pinning an older release tag would silently stop testing against what users actually run, and it breaks again on the next dropped package.
Re-adding
@streamparser/jsonto main would undo #25202 just for a test harness.Installing the release's exact dependencies at test time would need network access and a package install inside the test run, which is slow and flaky in CI.
Skipping files that can't load would drop the terminal and agent-session wire coverage, which matters most here.
The stand-in is narrow: it applies only to packages that cannot load, and it refuses at the point of use with the reason. None of today's wire suites run the release's JSON stream parsing, so no suite reaches a stand-in.
Known trade-off: if release code catches a stand-in's error and quietly falls back, a test could exercise that fallback instead of failing. The stand-in error names the package, so a log search finds it.
Temporary: the stand-in is a stopgap. The structural follow-up gives each release its own dependencies: one
pnpm install --frozen-lockfile --ignore-scriptsper distinct release lockfile into a cache next to the checkouts, with the checkouts linked to it and a warm-up step for cold installs. That removes the stand-ins, and it also stops old release code from silently running on newer package versions, which this PR does not address. No ticket yet.Linked Issue
None. This is CI breakage on main, found from run 37364132538.
Visual Proof
N/A: test-harness-only change, nothing visible to users.
Testing
New
release-missing-packages.unit.test.tsuses a synthetic release. Release code importing default, named and subpath exports of an uninstalled package loads, and each use throws with the package name. A package that resolves from the checkout keeps the real module. A package the current tree declares but cannot resolve still fails the import. Type-only imports, comment prose and anObjC.import('AppKit')string get no stand-in.New case in
release-checkout.unit.test.tsuses the real v1.4.221 tree in the default in-repository cache. ItsparseRipgrepMatchJsonruns normally on plain JSON, and itsparseDenseRipgrepMatchJson(the@streamparser/jsonpath) throws the stand-in message.Ablation: with no stand-ins written, both positive tests fail, and
cross-version-terminal-wirefails with the exact CI error (Cannot find package '@streamparser/json').Before the fix, the three CI-failing files failed locally with the same
Cannot find packageerror. After the fix, they pass locally together with both harness test files (5 files, 46 tests).I ran the full
tests/e2e/cross-version-wire/file set locally, the same set CI runs. Every remaining local failure wastar -xf timed out after 45000msduring a cold release extraction, or a 30 s test timeout behind it, on a machine at load average 60–80. None were import or assertion failures. The format bump makes every cached release tree cold, so CI is the authority here.Also ran:
oxlintandpnpm run check:code-quality:changed, both clean. Intypecheck:e2e, the only new error from this change is the.ts-extension import, the same onerelease-checkout.tsalready has. The extension is needed because plain-Node child processes import this chain directly.Platforms: macOS locally; CI covers Linux.
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Security: the stand-ins are written only inside the gitignored
tests/e2e/.cross-version-checkouts/cache. Cross-platform: paths usepath.join. SSH, mobile and the shipped app are not touched.PR author: @BrennanKB5.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)