Skip to content

Promise.all + ctx.task emits duplicate effects with collided journal sequence numbers #879

@rogelsm

Description

@rogelsm

R2 · SDK bug report — Promise.all + ctx.task journal collision

Title

Promise.all + multiple ctx.task emissions cause duplicate effects with collided journal sequence numbers

Summary

When a process function calls ctx.task from inside multiple Promise.all branches simultaneously, the babysitter SDK assigns the same stepId (and same journal sequence number) to two different effects. The journal ends up with two entries at the same seq; the state cache cannot reconcile; iterations stall or emit ghost effects.

Reproduction

Minimal process file:

import { defineTask } from '@a5c-ai/babysitter-sdk';

export const taskA = defineTask('parallel-a', () => ({ kind: 'agent', title: 'A' }));
export const taskB = defineTask('parallel-b', () => ({ kind: 'agent', title: 'B' }));
export const taskC = defineTask('parallel-c', () => ({ kind: 'agent', title: 'C' }));

export async function process(_inputs, ctx) {
  const [a, b, c] = await Promise.all([
    ctx.task(taskA, {}),
    ctx.task(taskB, {}),
    ctx.task(taskC, {}),
  ]);
  return { a, b, c };
}

Run with babysitter run:create ... && babysitter run:iterate .... Examine the journal — at least two events at the same seq.

Concrete evidence (from a real run)

In cookbook run 01KT797KHQ6RFVMJ55TPBQ1K26 (V2 lane epic, 2026-06-03):

  • Journal seq 4 had TWO entries: 000004.01KT798JNFRF4GCVFBBN0531TC.json (Lane B mockup task) and 000004.01KT798JNFRF4GCVFBBN0531TD.json (was Lane A's impl task)
  • babysitter run:iterate returned the error Journal sequence gap detected at 000004.01KT798JNFRF4GCVFBBN0531TD.json (expected 5, got 4)
  • Recovery required manually deleting one of the duplicate-seq files

The issue is that step IDs (S000003, S000004, etc.) appear to be assigned by Promise.all interleaving order, but the journal sequence numbers (000001, 000002, ...) collide because emission order is non-deterministic at sub-ms granularity.

Suggested fix direction

StepIds should derive from the invocation key (process file path + task name + cumulative invocation count for that task), not from monotonic emission order. Same for journal sequence numbers — they should advance once per persisted event, with deterministic ordering even when emission is concurrent.

OR: Document that Promise.all + ctx.task is unsupported, and recommend sequential for...of loops.

Environment

  • @a5c-ai/babysitter-sdk: (current version on cookbook, 2026-06-03)
  • Node 24
  • macOS / Claude Code harness

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingeffort:mediumMedium implementation effortpriority:highHigh priority issue affecting important workflowsrisk:highHigh implementation risk; requires broad integration validationsdkSDK package and CLI runtime

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions