Skip to content

fix(shared): resolve data-dir paths per call instead of capturing at import - #1490

Merged
backnotprop merged 2 commits into
backnotprop:mainfrom
FNDEVVE:fix/data-dir-per-call
Sep 10, 2026
Merged

fix(shared): resolve data-dir paths per call instead of capturing at import#1490
backnotprop merged 2 commits into
backnotprop:mainfrom
FNDEVVE:fix/data-dir-per-call

Conversation

@FNDEVVE

@FNDEVVE FNDEVVE commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Four modules evaluate getPlannotatorDataDir() at module scope and freeze every path derived from it, so they answer to whatever PLANNOTATOR_DATA_DIR was when the module first loaded — the same import-time capture storage.ts had until #1437/#1473, with the same consequences: 38 test files set the variable at runtime and Bun's test-file order is filesystem-dependent, so the captured value varies as test files are added.

  • packages/shared/improvement-hooks.tsDATA_DIR, HOOKS_BASE_DIR, LEGACY_BASE_DIR
  • packages/server/codex-review.tsDATA_DIR, DEBUG_LOG_PATH, SCHEMA_DIR, SCHEMA_FILE
  • packages/server/tour/tour-review.tsTOUR_SCHEMA_DIR, TOUR_SCHEMA_FILE
  • packages/server/guide/guide-review.tsGUIDE_SCHEMA_DIR, GUIDE_SCHEMA_FILE

Change

All of these now resolve per call. The *SchemaMaterialized booleans became per-path existence checks, so a data-dir switch after import materializes the schema in the new location instead of returning the old path forever. CODEX_REVIEW_SCHEMA_PATH (a re-export of the frozen string; no in-repo consumers) is replaced by getCodexReviewSchemaPath().

Verification

Bun 1.3.14, disposable HOME + temp PLANNOTATOR_DATA_DIR:

  • Regression tests import each module before mutating the env var (the reverse order passes with or without the fix), modeled on storage.test.ts: improvement-hooks.test.ts is rewritten in-process (the subprocess/fake-HOME workaround is no longer needed) and codex-command-builders.test.ts covers all three schema modules, including re-materialization after a mid-test dir switch.
  • Stash-the-fix control: with the four source edits stashed and the tests kept, 7 of the new assertions fail; restored, everything passes.
  • 88 pass / 0 fail across the 8 touched-area test files.
  • Pi vendoring re-run (apps/pi-extension/vendor.sh) and smoke-imported with the env set after import — the generated copies follow the new data dir (they are gitignored build output, so there is nothing to commit).

Deliberately out of scope: packages/server/browser.ts (IPC_REGISTRY) keeps its module-scope capture — same pattern, separate surface.

Fixes #1477

@backnotprop

Copy link
Copy Markdown
Owner

Deep review done. The sweep itself is solid: all four #1477 modules fully converted with no surviving derived-path constants, no consumer relied on freezing, vendoring clean, and per-module revert-verification confirms every module has a test that fails if it regresses (11/11 restored). Honest scoping too - browser.ts called out rather than smuggled.

One fix needed before merge: the schemaMaterializedexistsSync substitution changes schema-refresh semantics. The old flag meant "rewrite once per process", so every process start refreshed the schema files with the current binary's schema. existsSync writes only when absent - so a schema written by an older version persists forever (nothing prunes the data dir). Not hypothetical: #997 added a required summary field to the guide schema; replay that upgrade under this PR and Codex is --output-schema-forced into the old shape indefinitely, silently degrading guides. Suggested replacement preserving both properties (fresh per process AND data-dir switches):

const materializedSchemaPaths = new Set<string>();
async function ensureSchemaFile(): Promise<string> {
  const schemaPath = getCodexReviewSchemaPath();
  if (!materializedSchemaPaths.has(schemaPath)) {
    await mkdir(dirname(schemaPath), { recursive: true });
    await writeFile(schemaPath, CODEX_REVIEW_SCHEMA);
    materializedSchemaPaths.add(schemaPath);
  }
  return schemaPath;
}

Same shape in all three modules (using dirname(schemaPath) also removes the double getPlannotatorDataDir() call per materialization). Worth one added assertion: overwrite a schema file with stale bytes, call the builder, expect current bytes.

We'll file the follow-up for the one remaining frozen capture (packages/server/browser.ts:13 IPC_REGISTRY) on our side. Merge follows as soon as the Set-based materialization lands.

@backnotprop

Copy link
Copy Markdown
Owner

Pushed 75326e3b with the Set-based materialization from the review finding: all three modules (codex-review.ts, tour/tour-review.ts, guide/guide-review.ts) now guard schema writes with a per-path written-this-process Set instead of existsSync, so every process refreshes the schema files with the current binary's schema (restoring the old once-per-process semantics) while a PLANNOTATOR_DATA_DIR change after import still materializes into the new location. dirname(schemaPath) replaces the second getPlannotatorDataDir() call per materialization.

Added the requested regression assertion in codex-command-builders.test.ts: overwrite each schema file with stale bytes, call the builder, expect the current schema bytes — verified it fails under the existsSync guard and passes with the Set. bun test packages/server packages/shared 1890 pass / 0 fail, vendoring clean, typecheck clean.

@backnotprop
backnotprop merged commit e6822a2 into backnotprop:main Sep 10, 2026
24 checks passed
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.

resolve the data directory per call instead of capturing it at import

2 participants