fix(simulation): move queue-constants shim into scripts/ so Railway workers can resolve it (#3811 hotfix)#3818
Conversation
…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.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Greptile SummaryThis hotfix resolves a production crash loop on three Railway worker services by relocating
Confidence Score: 4/5Safe 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
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
|
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.
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:
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