Skip to content

fix(simulation): move queue-constants shim into scripts/ so Railway workers can resolve it (#3811 hotfix)#3818

Merged
koala73 merged 2 commits into
mainfrom
fix/3811-railway-worker-shim
May 18, 2026
Merged

fix(simulation): move queue-constants shim into scripts/ so Railway workers can resolve it (#3811 hotfix)#3818
koala73 merged 2 commits into
mainfrom
fix/3811-railway-worker-shim

Conversation

@koala73

@koala73 koala73 commented May 18, 2026

Copy link
Copy Markdown
Owner

Summary

PR #3811 broke three Railway worker services (`seed-forecasts`, `simulation-worker`, `deep-forecast-worker`) — they have been in a crash loop on every restart since merge with:

```
Error [ERR_MODULE_NOT_FOUND]: Cannot find module
'/server/_shared/_simulation-queue-constants.mjs'
imported from /app/seed-forecasts.mjs
```

Root cause: #3811 added a shared shim at `server/_shared/_simulation-queue-constants.mjs` and had `scripts/seed-forecasts.mjs` import it via `../server/_shared/...`. That works in dev and on Vercel (esbuild inlines), but the three Railway services use the nixpacks build with `root_dir=scripts`, which packages only `scripts/` contents into `/app/` in the container. The relative import escapes `/app/` at runtime and crashes the worker on startup.

Fix: Move the shim into `scripts/` (alongside the seeder that needs it). Update the 4 importers:

  • `scripts/seed-forecasts.mjs` → `'./_simulation-queue-constants.mjs'`
  • `server/_shared/simulation-queue.ts` → `'../../scripts/_simulation-queue-constants.mjs'`
  • `server/worldmonitor/forecast/v1/trigger-simulation.ts` → `'../../../../scripts/...'`
  • `server/worldmonitor/forecast/v1/get-simulation-outcome.ts` → `'../../../../scripts/...'`

esbuild inlines the shim into the Vercel bundle, so the cross-directory `../../scripts/...` path is fine on the server side. The shim's header documents WHY it lives in `scripts/` to prevent the next contributor from "tidying" it back into `server/_shared/`.

Regression test: `tests/scripts-railway-nixpacks-no-escape-import.test.mts`. BFS-walks the three Railway entry points (`seed-forecasts`, `process-simulation-tasks`, `process-deep-forecast-tasks`), follows every relative import inside `scripts/`, and fails if any resolved path escapes `scripts/`. Verified the test correctly fails with the broken import restored, then passes after the fix.

This wasn't caught by #3811's test suite because none of the new tests exercised a Railway-shipped resolution path — they ran under Vercel-bundler assumptions only.

Test plan

  • `npm run typecheck` — clean
  • `npm run typecheck:api` — clean
  • 30/30 simulation tests pass (27 existing + 3 new)
  • `node docker/build-handlers.mjs` — 0 failures, esbuild inlines shim from new path
  • Regression test fails with broken import restored, passes after fix
  • After merge, watch Railway logs for `seed-forecasts`, `simulation-worker`, and `deep-forecast-worker` to confirm container starts cleanly

…orkers can resolve it

#3811 introduced `_simulation-queue-constants.mjs` at `server/_shared/`
and had `scripts/seed-forecasts.mjs` import it via `../server/_shared/...`.
That works in dev and on Vercel (esbuild inlines), but the three Railway
services that run the seeder — `seed-forecasts`, `simulation-worker`,
`deep-forecast-worker` — use the nixpacks build with `root_dir=scripts`,
which packages only `scripts/` contents into `/app/` in the container.

The relative import resolves to `/server/_shared/_simulation-queue-
constants.mjs` at runtime — a path that does not exist in the container —
and crashes every worker on startup:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module
  '/server/_shared/_simulation-queue-constants.mjs'
  imported from /app/seed-forecasts.mjs

All three Railway services have been in a crash loop since #3811 merged.

Fix: move the shim into `scripts/` (where it ships alongside the seeder)
and update the 4 importers:

  - scripts/seed-forecasts.mjs          → './_simulation-queue-constants.mjs'
  - server/_shared/simulation-queue.ts  → '../../scripts/_simulation-queue-constants.mjs'
  - server/worldmonitor/forecast/v1/trigger-simulation.ts    → '../../../../scripts/...'
  - server/worldmonitor/forecast/v1/get-simulation-outcome.ts → '../../../../scripts/...'

esbuild bundles the shim inline at Vercel build time, so the cross-
directory `../../scripts/...` path is fine on the server side. The
`.d.mts` declaration moves alongside so TS module-resolution still
picks it up. The shim's own header now documents WHY it lives in
scripts/ to prevent the next contributor from "tidying" it back into
server/_shared/.

Regression test: tests/scripts-railway-nixpacks-no-escape-import.test.mts.
BFS-walks `scripts/seed-forecasts.mjs`, `scripts/process-simulation-tasks.mjs`,
and `scripts/process-deep-forecast-tasks.mjs` (the three Railway service
entry points), following every relative import inside `scripts/`, and
fails if any resolved path escapes `scripts/`. Verified that the test
correctly fails with the broken import restored, then passes after fix.

Verification:
  - typecheck (main + api): clean
  - simulation tests: 30/30 pass (27 existing + 3 new)
  - docker/build-handlers.mjs: completes with 0 failures
  - shim resolves at runtime from both old (.mjs entry) and esbuild path

Distinct from #3811's Vercel-Edge fix commit (Web Crypto port) — that
addressed a build-time bundler error; this addresses a runtime
container-packaging error that only fires on Railway. No tests in #3811
exercised a Railway-shipped resolution path, which is how this escaped
review. The new test closes that coverage gap for these three workers
and any future scripts/ entry added to ENTRY_POINTS.
@vercel

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview May 18, 2026 5:21pm

Request Review

@greptile-apps

greptile-apps Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This hotfix resolves a production crash loop on three Railway worker services by relocating _simulation-queue-constants.mjs from server/_shared/ into scripts/, the directory that nixpacks packages into /app/ for those containers. Four import paths are updated accordingly, and a regression test is added to BFS-walk the three Railway entry points and fail if any relative import escapes scripts/.

  • The import-path changes are correct across all four callers; esbuild continues to inline the shim for Vercel Edge builds, and the Railway workers now resolve the file without escaping /app/.
  • The regression test's BFS logic is sound and has been verified to catch the broken state; a minor dead-code branch (isBareOrNode guard in the BFS loop) has no effect on correctness.
  • The shim file itself was renamed with no content changes, leaving its header without the Railway-packaging rationale that three of the updated importers now cross-reference — a small documentation gap worth closing.

Confidence Score: 4/5

Safe to merge — the fix directly addresses the crash-loop root cause with no logic changes, and the new regression test catches regressions on the exact failure mode.

All four import paths are updated correctly, the shim's content is unchanged, and esbuild behaviour on Vercel is unaffected. The only issue is a broken documentation cross-reference: three importers point to 'see the header of scripts/_simulation-queue-constants.mjs for the Railway packaging reason,' but the shim's header (similarity index 100%, no content changes) contains no such note. This doesn't affect runtime behaviour but could mislead a future contributor into moving the file back.

scripts/_simulation-queue-constants.mjs — its header should be updated to carry the Railway packaging rationale that several importer comments already cross-reference it for.

Important Files Changed

Filename Overview
scripts/_simulation-queue-constants.mjs Renamed from server/_shared/ to scripts/ with no content changes (similarity 100%); the header still references cache-keys.ts as an importer (stale) and is missing the Railway packaging rationale that three importers cross-reference it for.
scripts/_simulation-queue-constants.d.mts Renamed from server/_shared/ to scripts/ with no content changes; type declarations remain correct and complete.
scripts/seed-forecasts.mjs Import updated from ../server/_shared/_simulation-queue-constants.mjs to ./_simulation-queue-constants.mjs; detailed Railway packaging note added to the comment block. Fix is correct.
server/_shared/simulation-queue.ts Import updated to ../../scripts/_simulation-queue-constants.mjs; the IMPORT PATH NOTE comment cross-references the shim header for the Railway packaging reason, but that explanation does not exist in the shim's (unchanged) header.
server/worldmonitor/forecast/v1/trigger-simulation.ts Import updated to ../../../../scripts/_simulation-queue-constants.mjs with an explanatory comment; same broken cross-reference to the shim header as simulation-queue.ts.
server/worldmonitor/forecast/v1/get-simulation-outcome.ts Import updated to ../../../../scripts/_simulation-queue-constants.mjs with an explanatory comment; same broken cross-reference to the shim header as other server-side importers.
tests/scripts-railway-nixpacks-no-escape-import.test.mts New regression test BFS-walks three Railway entry points and asserts no relative import escapes scripts/; logic is sound but the isBareOrNode guard in the BFS loop is dead code since collectRelativeImports already pre-filters to relative specs only.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph Railway["Railway nixpacks (root_dir=scripts → /app/)"]
        SF["scripts/seed-forecasts.mjs"]
        PST["scripts/process-simulation-tasks.mjs"]
        PDFT["scripts/process-deep-forecast-tasks.mjs"]
        SHIM["scripts/_simulation-queue-constants.mjs ✅ NOW HERE"]
    end

    subgraph Vercel["Vercel Edge (esbuild bundles server/)"]
        SQ["server/_shared/simulation-queue.ts"]
        TS["server/.../trigger-simulation.ts"]
        GSO["server/.../get-simulation-outcome.ts"]
    end

    SF -->|"'./_simulation-queue-constants.mjs'"| SHIM
    PST -.->|transitive| SF
    PDFT -.->|transitive| SF

    SQ -->|"'../../scripts/_simulation-queue-constants.mjs' (inlined by esbuild)"| SHIM
    TS -->|"'../../../../scripts/_simulation-queue-constants.mjs' (inlined by esbuild)"| SHIM
    GSO -->|"'../../../../scripts/_simulation-queue-constants.mjs' (inlined by esbuild)"| SHIM

    OLD["server/_shared/_simulation-queue-constants.mjs ❌ REMOVED"]
    style OLD fill:#ffcccc,stroke:#cc0000
Loading

Comments Outside Diff (1)

  1. scripts/_simulation-queue-constants.mjs, line 1-14 (link)

    P2 Shim header missing the Railway-packaging note it's referenced for

    Three of the changed files add // See scripts/_simulation-queue-constants.mjs header cross-references that direct future readers here for the Railway packaging explanation, but this file was renamed with similarity index 100% — its header is unchanged and contains no such note. The explanation lives in the importer comments instead. This means the next contributor who opens the shim directly (e.g., to "tidy" it back to server/_shared/) won't find any in-file warning. The PR description explicitly calls out that the header was supposed to carry this rationale.

Reviews (1): Last reviewed commit: "fix(simulation): move queue-constants sh..." | Re-trigger Greptile

Comment thread tests/scripts-railway-nixpacks-no-escape-import.test.mts
1. Shim header missing the Railway-packaging rationale (P2).
   Three importers (server/_shared/simulation-queue.ts and the two
   forecast/v1 handlers) added "see the header of
   scripts/_simulation-queue-constants.mjs for the Railway packaging
   reason" cross-references in their import comments, but the shim
   itself was renamed with similarity index 100% — its header was
   the original Edge-bundling note with no mention of why it lives
   in scripts/ now. My header edit was made locally but never staged
   before the previous commit, so the published file shipped without
   the rationale. Fix: rewrite the header to lead with "DO NOT MOVE
   THIS FILE BACK TO server/_shared/", spell out the nixpacks
   root_dir=scripts mechanism, name the three affected Railway
   services, and link the regression test + incident PRs. A future
   contributor opening the shim to "tidy" it will now find an
   in-file warning instead of needing to chase importer comments.

2. isBareOrNode dead code in the BFS loop (P2).
   collectRelativeImports already pre-filters to `./` and `../`
   specs only, so isBareOrNode(spec) in the loop body always
   returned false and the `continue` was never reached. Removed
   both the unused helper and the loop guard.

Tests still 3/3 pass; typecheck:api clean.
@koala73
koala73 merged commit f513c01 into main May 18, 2026
10 checks passed
@koala73
koala73 deleted the fix/3811-railway-worker-shim branch May 18, 2026 17:29
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