Repository navigation
fix(runtime): wait out a registry propagation gap instead of losing the run to it - #380
Conversation
R54 died 97 seconds in, before a single task node ran. npm resolved `@ai-sdk/provider` to 4.0.7, published 128 seconds earlier, and 404ed fetching its tarball: the packument entry goes live before the tarball reaches every CDN edge. Seven minutes later the same URL served 200. The generated workspace installs with `--package-lock=false`, so every unpinned transitive dependency of the runner is re-resolved at install time and can land inside that window. Pinning cannot close it — the exposure is the whole transitive set, not one named package — so retry instead, while the registry reports something waiting can fix. Conservative on purpose: a 404 on a tarball URL is a propagation gap, a 404 on a package or version name is a real dependency error and still fails fast, as do aborts and timeout kills. The durable node worker ran the same unretried install, where the cost is a node attempt and a sandbox spin-up rather than the run; it shares the helper now. Fixes #379 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Independently confirmed this PR's root cause against the R54 volume (
Heads-up: this PR and #384 will conflict, though both currently report Both branch from Whichever merges first leaves the other conflicted. The changes are complementary, not competing — this one wraps the call, #384 replaces its argument array — so the second one through should combine rather than take a side: await withTransientNpmRegistryRetry(() =>
runChecked(
"install-smithers",
"npm",
smithersDependencyInstallArgs({ prefix: path.join(projectRoot, ".smithers") }),
projectRoot
)
);and in Worth keeping both: the cutoff in #384 shrinks the window, but only a retry survives a 5xx or a network drop on an already-resolved tarball. Read-only diagnosis; I have not modified either branch. |
Composes the transient-registry install retry with the #384 resolution cutoff: both installers now call smithersDependencyInstallArgs() inside withTransientNpmRegistryRetry(), so --before and the retry are on the same path. Keeps both PRs' tests.
Fixes #379.
What broke
R54 (
aave-v4-v0012-main-issue368fix-r54-invariant-only) died 97 seconds after start, before a single task node ran — all 23 leftpending,project-discoverynever pastwait_reason: ready. The run-level event wasworkflow-submit-failed:@ai-sdk/provider@4.0.7was published at23:48:58.306Z. The install started at23:51:06and 404ed. At23:58the same URL returned 200. The packument entry goes live before the tarball reaches every registry CDN edge; inside that window npm resolves the version and then hard-fails fetching it.The generated workspace installs with
--package-lock=false, so every unpinned transitive dependency is re-resolved per install —smithers-orchestrator@0.32.0→ai@^7.0.10→@ai-sdk/provider@^4floats onto whatever shipped most recently. Pinning does not close this: the exposure is the entire transitive set, not one named package.The change
withTransientNpmRegistryRetrywraps the runner install and retries at 1s/5s/15s/45s while the failure is one the registry will fix on its own — a 404 on a tarball URL, a network error, or a registry 5xx. Up to ~66s of waiting against a multi-hour campaign.Deliberately conservative, so a real problem still fails fast:
ERESOLVEand anything unrecognized — not retriedtimeoutMs— not retried (the deadline that stopped it has already passed)The last error propagates unwrapped, so callers still report the registry's own diagnostic.
packages/modal/src/node-worker.tsran the same unpinned, unretried install per durable node — same defect, smaller blast radius (a node attempt and a sandbox spin-up). It shares the helper rather than duplicating the logic, which is whypackages/runtime/src/index.tsgains one export line.Tests
startRun retries a workflow runner install the registry fails transiently— verified red onorigin/main, and the red diagnostic is byte-for-byte R54'sWORKFLOW_SUBMISSION_FAILEDpayload.startRun does not retry a workflow runner install the registry rejects permanently— asserts exactly one npm invocation, pinning the fail-fast boundary.isTransientNpmRegistryFailure separates a waitable registry gap from a real dependency error— unit coverage for each classification above.pnpm --filter @ultrafuzz/runtime test→ 442 tests, 427 pass, 0 fail, plus the 14 bun contract tests.pnpm --filter @ultrafuzz/modal test→ 352 pass.prettier --checkclean on all touched files. Both packages build.Heads-up for in-flight branches
This touches
packages/modal/src/node-worker.ts,packages/runtime/src/index.tsandpackages/runtime/src/smithers.ts. Open PRs #366 (release/v0.1.0) and #165 (bench/deepseek-v4-flash-results) carry changes to those paths as accumulation branches and will want a rebase after this merges. It does not touchworkflow.tsx,workflow-sync.ts,node-provider.tsorpublic-worker.ts.🤖 Generated with Claude Code
Greptile Summary
The PR adds a shared retry helper for transient npm registry failures and applies it to runtime and Modal dependency installation.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Install Smithers dependencies] --> B{Install succeeds?} B -->|Yes| C[Continue workflow submission] B -->|No| D{Transient registry failure?} D -->|No| E[Propagate original error] D -->|Yes| F{Retry available?} F -->|No| E F -->|Yes| G[Wait with backoff] G --> AReviews (2): Last reviewed commit: "Merge origin/main into fix/transient-npm..." | Re-trigger Greptile
Context used: