Skip to content

Bug: Nested batch() — inner batch changes survive outer batch throw #1743

@palak170306-design

Description

@palak170306-design

Which package?

@termuijs/store

What happened?

batch() is documented to be atomic — if the callback throws, all state changes within it
should be rolled back. This holds for flat (non-nested) batches. But when batch() calls
are nested and the outer batch throws, inner batch changes are not rolled back.

Root cause: when the inner batch completes without throwing, _batchDepth drops back to 0
and flushBatch(false) is called, which schedules a queueMicrotask to commit inner changes.
This microtask is already enqueued before the outer batch throws. When the outer batch throws,
flushBatch(true) correctly rolls back outer changes, but has no way to cancel the already-
scheduled microtask that commits the inner batch's changes.

Expected: If the outer batch() throws, all state changes — including those from nested
inner batches — are rolled back completely.
Actual: Inner batch changes survive the outer batch throw and are committed.

Steps to reproduce

```typescript
import { createStore, batch } from '@termuijs/store';

const store = createStore(() => ({ x: 0, y: 0 }));

try {
batch(() => {
// Inner batch completes successfully and schedules a microtask commit
batch(() => {
store.setState({ x: 1 });
});

    store.setState({ y: 2 });

    throw new Error('outer batch failed');
});

} catch {}

// Allow microtask queue to flush
await Promise.resolve();

console.log(store.getState());
// Actual: { x: 1, y: 0 } ← inner change survived
// Expected: { x: 0, y: 0 } ← full rollback
```

The core issue is in flushBatch: when _batchDepth returns to 0 at the end of an inner
batch, it cannot tell it is nested inside an outer batch that may still fail.

Environment

  • TermUI version: latest (main branch)
  • Node.js 18+
  • Any OS / terminal

GSSoC contributor?

  • Yes. You contribute under GSSoC 2026.
  • No.

Metadata

Metadata

Labels

assignedIssue claimed by a contributor.type:bug+10 pts. Bug fix.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions