Skip to content

Run the documented reconciliation CLI under the pinned Node runtime - #207

Merged
Nitjsefnie merged 15 commits into
mainfrom
fix/12-reconcile-cli-runtime
Sep 7, 2026
Merged

Run the documented reconciliation CLI under the pinned Node runtime#207
Nitjsefnie merged 15 commits into
mainfrom
fix/12-reconcile-cli-runtime

Conversation

@Nitjsefnie

@Nitjsefnie Nitjsefnie commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Summary

pnpm reconcile died during module loading under the repository's pinned Node 24.17.0, so the documented manual reconciliation route did not run at all. This makes both documented invocations start and run under that runtime, using the documented syntax.

Related Issues and Pull Requests

Fixes #12

Issue 191 reported the same failure with a fuller reproduction and was closed into issue 12 as a duplicate before this branch started.

Changes

  • package.json: the reconcile script now runs Node with --experimental-transform-types and preloads an alias-resolution hook. db:migrate and the release:* scripts are untouched.
  • scripts/register-path-aliases.ts (new): a synchronous registerHooks resolver mapping @/… onto the source tree, trying .ts, .tsx and /index.ts, then delegating to Node's normal resolution. The source root comes from the hook's own import.meta.url, never from the working directory, and a genuinely missing module still fails with ERR_MODULE_NOT_FOUND rather than being swallowed.
  • scripts/reconcile.ts: the argument shape is parsed before production dependencies are constructed, so an argument error is reported instead of being masked by the database-configuration error. Both exported overloads, the injectable ReconcileCliDependencies seam and the usage message are unchanged.
  • README.md: removes the spurious -- from the selected-repository example.
  • tests/deploy/reconcile-cli.test.ts (new): extracts the pnpm reconcile commands from the README's Reconciliation block and runs each as a real child process, covering module loading, argument parsing, the accepted repository-name shape, and a successful run against PostgreSQL.
  • tests/deploy/register-path-aliases.test.ts (new): pins the hook's working-directory independence, its delegation to default resolution, and its missing-module behaviour.

Three defects sat behind one symptom. Node's strip-only TypeScript mode cannot compile parameter properties (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX from src/lib/fold/postgres-store.ts), and once those are transformed it cannot resolve the 26 @/… alias imports across the 21 files reachable from the entry point (ERR_MODULE_NOT_FOUND: Cannot find package '@/lib'), because compilerOptions.paths is honoured only by the bundler and vitest. Separately, pnpm 10.33.0 forwards a literal --, so the documented pnpm reconcile -- --repository <owner>/<name> produced a three-element argv that the CLI's strict two-element shape rejects.

The repair is at the runtime rather than in the source graph by design. Rewriting the three files that use parameter properties would fix this entry point and break again on the next parameter property anyone adds, and two of those files belong to concurrent work.

Testing

Full gate run bare on the rebased head: pnpm db:migrate && pnpm test --run && pnpm lint && pnpm typecheck && pnpm build, with every Docker-backed suite running rather than skipping.

Beyond the gate, the regression suite was verified by mutation rather than by passing. Seventeen mutations that reintroduce a real defect were each planted in the real modules and confirmed to turn the suite red — restoring the old package script, removing either Node flag, removing the transform flag while supplying it from NODE_OPTIONS, from an XDG_CONFIG_HOME pnpm rc file, or from a global pnpmfile lifecycle shell, rooting the alias hook at process.cwd(), constructing dependencies before parsing, passing a wrong repository id to the production callback, replacing that callback with fabricated skip summaries, narrowing the accepted repository-name characters, both README command-duplication mutants, rewrapping the documented command without a continuation, renaming the README heading, joining a # word onto the repository argument through a continuation, and making the CLI silently write nothing. Five valid documentation edits were confirmed not to break it: quoting the placeholder either way, wrapping with a backslash continuation, naming a seeded repository, and appending a trailing shell comment.

The successful-run coverage uses a real PostgreSQL container and no network: repositories on cooldown produce skip summaries and no reconciliation_runs rows, while a repository whose cooldown has expired under a sponsor with no OAuth token makes the CLI exit 1 and persist a FAILED row — which is what distinguishes real reconciliation from a callback that only prints plausible summaries.

Follow-ups / Known Limitations

  • The persisted-attempt coverage above applies to the selected-repository form only. A callback that fabricated skip summaries for the all-repositories form while leaving the selected form intact would still pass; closing that means extending the same tokenless fixture to the no-argument invocation.
  • The README command extractor is not a shell. It handles quoting, backslash continuations and word-boundary comments, and it throws on syntax it will not honour faithfully, but a command inside a zero-iteration shell loop would be extracted and run anyway, and a backslash followed by CRLF is treated as a continuation where Bash would not.
  • The child-environment allowlist stops configuration-file and environment injection of Node flags, but it cannot defend against a node shim earlier on PATH, since the child must be able to find Node at all.
  • The alias hook is preloaded only by the reconcile script, not shared with db:migrate or the release:* scripts. Sharing it is one line each, and would close the whole class the day someone adds an @/… import to scripts/release.ts — where the failure would land mid-deploy. It is deliberately not done here: that line also carries --experimental-transform-types into the deploy path, making a release switch depend on an experimental Node flag, and scripts/release.ts imports only node: builtins today. The next person to add an aliased import to a Node-run entry point should revisit this rather than rediscover it.
  • Running the reconcile script now prints Node's ExperimentalWarning: Transform Types on stderr. Nothing here is warning-free output.

Footer

Generated by GPT-6 Astra (implementation, testing, review), Claude Opus 5 (brief, review, integration)

Nitjsefnie and others added 15 commits September 7, 2026 08:42
Preload source alias resolution and transform TypeScript syntax. Validate CLI arguments before constructing database dependencies and smoke-test the documented pnpm commands.

Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
@Nitjsefnie
Nitjsefnie force-pushed the fix/12-reconcile-cli-runtime branch from 4b278de to 0bab91c Compare September 7, 2026 06:46
@Nitjsefnie
Nitjsefnie marked this pull request as ready for review September 7, 2026 06:49
@Nitjsefnie
Nitjsefnie merged commit 25912cb into main Sep 7, 2026
2 checks passed
@Nitjsefnie
Nitjsefnie deleted the fix/12-reconcile-cli-runtime branch September 7, 2026 06:50
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.

The documented reconciliation command fails before parsing arguments

1 participant