Skip to content

sagas: generated runtime glue registers no KV adapter — saga runner crashes on a default scaffold #1184

Description

@rickylabs

The sagas scaffold emits sagas/runtime.ts with no KV adapter registration, while the AppHost provisions a Redis/Garnet cache resource by default. The saga background processor therefore crashes on startup in a freshly scaffolded project.

Found by the wave-4 DeepSeek attribution-control run on published 0.0.4 (/home/codex/repos/wave4-deepseek-004), building a deploy queue. This is downstream of #1064/#1065/#1066 — those fixed the saga engine; the generated glue for the durable store is still broken.

Reproduction

Scaffold a project with the sagas plugin and the default cache backend, then start the AppHost. The sagas resource dies immediately:

[sagas] [sys] Starting process...: Cmd = /home/codex/.deno/bin/deno, Args = ["run",
  "--minimum-dependency-age=0", "--node-modules-dir=none", "--unstable-worker-options",
  "--unstable-kv", "--allow-all", "--watch", "sagas/runtime.ts"]
[sagas] Watcher Process started.
[sagas] error: Uncaught (in promise) KvConnectionError: Redis/Garnet KV provider was
  auto-detected but the Redis adapter is not registered. Add `import '@netscript/kv/redis';`
  to your service entrypoint to opt-in. Frontend/SSR apps should use Deno KV instead
  (set CACHE_PROVIDER=denokv).
      throw new KvConnectionError(
            ^
    at initializeKv (https://jsr.io/@netscript/kv/0.0.4/application/shared.ts:221:13)
    at getKv (https://jsr.io/@netscript/kv/0.0.4/application/shared.ts:126:17)
    at openSagaRuntimeKv (https://jsr.io/@netscript/plugin-sagas-core/0.0.4/src/stores/kv-saga-store.ts:40:31)
    at async startSagaRunner (https://jsr.io/@netscript/plugin-sagas/0.0.4/src/runtime/saga-runner.ts:77:3)
    at async runSagaRunner (https://jsr.io/@netscript/plugin-sagas/0.0.4/src/runtime/saga-runner.ts:92:3)
    at async file:///<project>/sagas/runtime.ts:9:3

The saga API stays up but returns an empty set forever — GET /api/v1/sagas/sagas{"sagas":[],"total":0,...} — so the failure reads as "no sagas yet" from the outside.

Cause

plugins/sagas/src/adapter/resources/glue/runtime.stub.ts emits a sagas/runtime.ts whose only import is:

import { runSagaRunner } from '@netscript/plugin-sagas/runtime';

No KV adapter is registered. openSagaRuntimeKv then calls getKv(), which auto-detects the Aspire-provisioned Redis/Garnet resource and throws because no adapter was opted in. Nothing under plugins/sagas/src imports @netscript/kv/redis outside tests.

@netscript/kv/redis is a valid export at 0.0.4 (./redis./redis.ts), so the fix is available — it is simply never wired into the generated glue.

Why the error message alone is not enough

The message prescribes the correct fix, but sagas/runtime.ts is generator-emitted glue. A hand-added import '@netscript/kv/redis'; is at risk of being overwritten on the next regeneration, so the import belongs in the stub rather than in user edits.

Impact

Any scaffolded project that uses the sagas plugin with the default cache backend has a saga runner that will not start. The observed cost to a fresh agent was roughly 10 minutes of diagnosis plus a behavioural workaround: pinning the saga KV provider to a local Deno KV file, which gets restart-durability but silently gives up the shared durable store across processes.

Acceptance

  • The emitted sagas runtime glue registers the Redis adapter (or otherwise resolves the provider) so a default scaffold starts cleanly against the Aspire cache resource

  • A scaffolded project with the sagas plugin and the default cache backend starts its sagas resource without a KvConnectionError

  • A test that fails without the fix covers the generated-glue path, not only the engine

  • The CACHE_PROVIDER=denokv path stays working for frontend/SSR projects

  • A saga is driven through its full lifecycle end to end in a locally scaffolded project — not a unit test — and the run is proven with OTEL evidence

Verification must go further than last time

#1064/#1065/#1066 closed with passing tests and the saga surface was still broken on a real scaffold. Engine-level coverage is what let this through: it never scaffolded a project, never started an AppHost, and never watched a saga actually run. Repeating that shape here would close this issue with the same gap.

Required before this is called done:

  1. Scaffold a fresh project locally with the sagas plugin and the default cache backend. Not a fixture, not a test harness — the thing a user gets.
  2. Start the AppHost and confirm the sagas resource reaches a genuinely healthy state. state: Running with healthStatus: Healthy and an empty healthReports array means nothing was checked — that is not proof.
  3. Drive a saga through its whole lifecycle: start, step through, and reach a terminal state. Include a compensating path, since sagaCompensate (sagas: sagaCompensate effects are silently dropped - createDurableSagaRuntime wires no compensator by default #1065) is exactly the kind of thing engine tests passed and reality did not.
  4. Prove it with traces and spans, via aspire otel traces / aspire otel spans / aspire otel logs against the failing-then-passing resource. The spans must show the saga steps actually executing, and correlation must hold across them — .correlate() (sagas: engine ignores .correlate() extractor - all workflows collapse onto one shared instance #1066) collapsed every workflow onto one instance and engine tests did not catch it.
  5. Show the RED first. Capture the KvConnectionError on the unfixed scaffold, then the same scenario passing after the fix. A green run alone does not demonstrate the defect was the thing fixed.
  6. Verify the artefact, not the exit code. A piped command reports the last stage's status; confirm the files exist and the boundary actually ran.

Restart durability is part of the lifecycle, not a separate nicety: restart the process and confirm the saga state is still correct afterwards, since the whole reason the durable store exists is that it survives a restart.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions