Skip to content

fix: ship sqlite runtime modules + packaging guard + Chroma error logging#1

Merged
mmackelprang merged 7 commits into
mainfrom
fix/chroma-observation-sync-packaging
Jul 2, 2026
Merged

fix: ship sqlite runtime modules + packaging guard + Chroma error logging#1
mmackelprang merged 7 commits into
mainfrom
fix/chroma-observation-sync-packaging

Conversation

@mmackelprang

Copy link
Copy Markdown
Owner

Fixes the 13.9.x regression where observation → Chroma vector sync was 100% broken: worker-service.cjs lazy-requires ../sqlite/* modules the build never emitted / the npm allowlist never shipped, so every observation threw MODULE_NOT_FOUND (swallowed to {}).

Changes (hybrid A/B)

  • parseFileListstatic import in ChromaSync.ts (inlined into the worker bundle; kills the runtime-require foot-gun on the hot path; SDK bundle stays free of bun:sqlite).
  • SessionStoreemitted to plugin/sqlite/SessionStore.cjs + added to package.json#files; backfill short-circuits to the injected store.
  • Static packaging guard in build-hooks.js + clean-room npm pack PART 3 — every relative require("../…") in plugin/scripts/*.cjs must resolve inside the tarball, or the build/pack fails.
  • Logging fix: isErrorLike() in logger.ts so Bun's non-Error ResolveMessage serializes its .message/.stack instead of {}; catch sites normalized in worker-service.ts / ResponseProcessor.ts / SessionRoutes.ts.
  • Regression test + negative control.

Gates

typecheck ✅ · build (guard) ✅ · check:sdk-bundle (no bun:sqlite leak) ✅ · smoke:clean-room PART 3 ✅ · npm pack asserts SessionStore.cjs in / .js out ✅ · Bun runtime resolve ✅ · regression 4/4 ✅. Single-user local mode unaffected. Live-Chroma UAT validated separately (observation flow + backfill init); the only unrun step was DNS-env-blocked, not a defect.

Consolidates the approaches from community PRs thedotmack#3102 / thedotmack#3108 / thedotmack#3116.

Co-authored-by: bionicbutterfly13 bionicbutterfly13@users.noreply.github.com
Co-authored-by: KaizenPrompt KaizenPrompt@users.noreply.github.com
Co-authored-by: Garcia6l20 Garcia6l20@users.noreply.github.com

mmackelprang and others added 7 commits July 2, 2026 10:21
…ace MODULE_NOT_FOUND errors

parseFileList is pure (only imports logger, no bun:sqlite), so make it a
static import inlined into the worker bundle instead of a lazy createRequire
loader — the more robust fix for the observation hot path. SessionStore still
requires the lazy loader (it pulls bun:sqlite), so short-circuit it behind the
injected store override. Make MODULE_NOT_FOUND-class errors visible: Bun's
ResolveMessage/BuildMessage are not instanceof Error and serialize as {}, so
treat any object with a string message as error-like, and normalize the
fire-and-forget catch sites that swallowed them.

Co-authored-by: bionicbutterfly13 <bionicbutterfly13@users.noreply.github.com>
Co-authored-by: KaizenPrompt <KaizenPrompt@users.noreply.github.com>
Co-authored-by: Garcia6l20 <Garcia6l20@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WSGNPpdcNkTexMTXf9y2f
…n/sqlite, add relative-require guards

The worker reaches SessionStore via a runtime createRequire('../sqlite/
SessionStore.js') that esbuild leaves as a bare string literal, so the build
must emit SessionStore.js as a loose sibling of the worker bundle (thedotmack#3091/
thedotmack#3092/thedotmack#3107). Allowlist plugin/sqlite in the npm tarball so it actually ships.
Add two static guards that assert every relative require target in
plugin/scripts/*.cjs resolves to an emitted sibling: a fast build-time check
(build-hooks) and a tarball check (smoke:clean-room PART 3).

Co-authored-by: bionicbutterfly13 <bionicbutterfly13@users.noreply.github.com>
Co-authored-by: KaizenPrompt <KaizenPrompt@users.noreply.github.com>
Co-authored-by: Garcia6l20 <Garcia6l20@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WSGNPpdcNkTexMTXf9y2f
… require

Loose sibling of the worker bundle that ChromaSync's lazy loader resolves at
runtime. bun:sqlite kept external so it stays out of the SDK bundle.

Co-authored-by: bionicbutterfly13 <bionicbutterfly13@users.noreply.github.com>
Co-authored-by: KaizenPrompt <KaizenPrompt@users.noreply.github.com>
Co-authored-by: Garcia6l20 <Garcia6l20@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WSGNPpdcNkTexMTXf9y2f
…e resolution

Asserts SessionStore.js is emitted next to the worker bundle and resolves the
way ChromaSync requires it, and that parseFileList is inlined (no
../sqlite/observations/files.js require survives, and no loose files.js ships).

Co-authored-by: bionicbutterfly13 <bionicbutterfly13@users.noreply.github.com>
Co-authored-by: KaizenPrompt <KaizenPrompt@users.noreply.github.com>
Co-authored-by: Garcia6l20 <Garcia6l20@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WSGNPpdcNkTexMTXf9y2f
…st + logger fix

worker-service.cjs now inlines parseFileList (Task 1) and no longer emits a lazy
createRequire('../sqlite/observations/files.js') — leaving the old bundle
committed would reference a module the build deliberately stops shipping. The
other bundles pick up the logger isErrorLike hardening (Task 6). External require
sets are unchanged across all five (no dependency drift). Repo convention ships
rebuilt bundles alongside source changes (cf. 29af028).

Co-authored-by: bionicbutterfly13 <bionicbutterfly13@users.noreply.github.com>
Co-authored-by: KaizenPrompt <KaizenPrompt@users.noreply.github.com>
Co-authored-by: Garcia6l20 <Garcia6l20@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WSGNPpdcNkTexMTXf9y2f
…es build

Pre-merge review found the build-time guard ran right after emitting
SessionStore.js but before server-service/mcp-server/context-generator/
transcript-watcher were rebuilt, so it scanned stale copies of 4 of the 5
bundles — defeating the "just-built" intent for those targets (smoke:clean-room
PART 3 still backstopped the real ship gate). Move the single guard call to
after all five plugin/scripts esbuild outputs are written so it scans fresh
output for every bundle. Bundle contents are unchanged by this move.

Co-authored-by: bionicbutterfly13 <bionicbutterfly13@users.noreply.github.com>
Co-authored-by: KaizenPrompt <KaizenPrompt@users.noreply.github.com>
Co-authored-by: Garcia6l20 <Garcia6l20@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WSGNPpdcNkTexMTXf9y2f
plugin/sqlite/SessionStore.js shipped under plugin/package.json's
"type":"module": it works under Bun (the real worker runtime) but Node
refuses to require() a .js file inside an ESM package. Emitting it as
.cjs removes that latent Node-consumer foot-gun — cheap and strictly
safer; nothing about the Bun path changes.

- scripts/build-hooks.js: emit plugin/sqlite/SessionStore.cjs (same
  esbuild opts: external ['bun:sqlite'], CJS format).
- src/services/sync/ChromaSync.ts: loadSessionStoreCtor() runtime
  createRequire specifier "../sqlite/SessionStore.js" -> ".cjs" (the
  type-only import stays ".js" — TS resolves it to the .ts source).
- Delete the stale committed plugin/sqlite/SessionStore.js (renamed to
  .cjs) so we never ship both.
- Regenerate plugin/scripts/*.cjs; only worker-service.cjs changes
  (ChromaSync is reachable only from the worker bundle).
- Regression test: resolve ../sqlite/SessionStore.cjs; negative control
  re-verified (remove file -> MODULE_NOT_FOUND).

Gates green: typecheck, build (relative-require guard resolves
../sqlite/SessionStore.cjs), check:sdk-bundle (no bun:sqlite leak),
smoke:clean-room PART 3 (tarball scan finds SessionStore.cjs), npm pack
(.cjs in tarball, .js absent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019WSGNPpdcNkTexMTXf9y2f
Copilot AI review requested due to automatic review settings July 2, 2026 18:30
@mmackelprang mmackelprang merged commit f99aac3 into main Jul 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a regression where the worker bundle relied on runtime createRequire('../sqlite/...') for SQLite modules that were not emitted/shipped, breaking Chroma vector sync with MODULE_NOT_FOUND errors that were effectively invisible due to {}-style serialization.

Changes:

  • Switches parseFileList to a static import in ChromaSync.ts (so it’s inlined into the worker bundle) while keeping SessionStore as a lazily-required, separately emitted plugin/sqlite/SessionStore.cjs.
  • Adds build- and pack-time “relative require closure” guards to prevent shipping bundles with unresolved sibling requires.
  • Improves error logging/normalization so Bun’s non-Error resolve/build failures produce useful messages (and adds a regression test).

Reviewed changes

Copilot reviewed 11 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/worker-service-lazy-sqlite-modules.test.ts Regression test ensuring SessionStore.cjs is present/resolvable and parseFileList is inlined (no loose files emitted).
src/utils/logger.ts Adds isErrorLike() and updates error formatting/routing behavior for Bun non-Error thrown types.
src/services/worker/http/routes/SessionRoutes.ts Normalizes caught values into Error when logging Chroma prompt sync failures.
src/services/worker/agents/ResponseProcessor.ts Normalizes caught values into Error when logging Chroma sync failures.
src/services/worker-service.ts Normalizes caught values into Error for background/backfill error logging.
src/services/sync/ChromaSync.ts Uses static parseFileList import; updates lazy require target to SessionStore.cjs.
scripts/smoke-clean-room.cjs Adds PART 3 tarball-time guard to validate relative require targets in packed plugin/scripts/*.cjs.
scripts/build-hooks.js Emits plugin/sqlite/SessionStore.cjs and adds build-time relative-require guard over plugin/scripts/*.cjs.
plugin/sqlite/SessionStore.cjs Adds the emitted CJS runtime SQLite module to be required by the worker at runtime.
plugin/scripts/context-generator.cjs Rebuilt bundle incorporating updated logger behavior (artifact update).
package.json Ensures plugin/sqlite is included in the published package files allowlist.

Comment thread src/utils/logger.ts
Comment on lines 341 to 350
private routeErrorToSink(message: string, context?: LogContext, data?: any): void {
try {
if (!errorSink || !(data instanceof Error)) return;
if (!errorSink || !isErrorLike(data)) return;
// Pass the message as context so the sink can fingerprint on it too; the
// sink (captureException) scrubs everything through error-scrub /
// scrubProperties, so an unsafe message here cannot leak — but `message`
// is not whitelisted, so it is dropped by scrubProperties anyway. We pass
// only the Error itself; context is intentionally minimal.
errorSink(data);
errorSink(data instanceof Error ? data : new Error(data.message));
} catch {
Comment on lines +344 to +372
// those parts cannot catch a missing sibling. Statically assert every relative
// require target inside the PACKED plugin/scripts/*.cjs resolves inside the
// tarball. This is the guard that would have caught 13.9.x shipping without
// plugin/sqlite/.
function checkPluginRelativeRequires(failures, pkgRoot) {
log('\nPART 3 — plugin relative-require closure (#3091/#3092/#3107 guard)');

const scriptsDir = path.join(pkgRoot, 'plugin', 'scripts');
if (!fs.existsSync(scriptsDir)) {
failures.push(`packed tarball is missing plugin/scripts (${scriptsDir})`);
return;
}

const RELATIVE_SPECIFIER = /["'](\.\.?\/[A-Za-z0-9_./-]+\.(?:c?js))["']/g;
let checked = 0;
for (const entry of fs.readdirSync(scriptsDir)) {
if (!entry.endsWith('.cjs')) continue;
const filePath = path.join(scriptsDir, entry);
const content = fs.readFileSync(filePath, 'utf8');
const seen = new Set();
let m;
while ((m = RELATIVE_SPECIFIER.exec(content)) !== null) {
const spec = m[1];
if (seen.has(spec)) continue;
seen.add(spec);
checked++;
const resolved = path.resolve(path.dirname(filePath), spec);
if (!fs.existsSync(resolved)) {
failures.push(
Comment thread scripts/build-hooks.js
Comment on lines +67 to +83
function assertPluginRelativeRequiresResolve(scriptsDir) {
const RELATIVE_SPECIFIER = /["'](\.\.?\/[A-Za-z0-9_./-]+\.(?:c?js))["']/g;
const failures = [];
for (const entry of fs.readdirSync(scriptsDir)) {
if (!entry.endsWith('.cjs')) continue;
const filePath = path.join(scriptsDir, entry);
const content = fs.readFileSync(filePath, 'utf-8');
const seen = new Set();
let m;
while ((m = RELATIVE_SPECIFIER.exec(content)) !== null) {
const spec = m[1];
if (seen.has(spec)) continue;
seen.add(spec);
const resolved = path.resolve(path.dirname(filePath), spec);
if (!fs.existsSync(resolved)) {
failures.push(`${entry} requires "${spec}" but ${path.relative(scriptsDir, resolved)} was not emitted`);
}
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.

2 participants