Skip to content

Legacy runFiber can recover work that already finished #2305

Description

@ben-reitz

What happens

If a legacy runFiber() finishes but deleting its bookkeeping row fails, the next recovery scan treats that leftover row as interrupted work and calls onFiberRecovered().

A custom recovery hook could repeat something that already happened, such as sending a message. The SDK does not automatically rerun the original function; the default hook only logs a warning.

How serious is it?

Low priority; raised by Devin in release PR #2251 as a pre-existing issue unrelated to that PR.

  • Cleanup failures have been reported in production (runFiber: a throw from the finally block discards the fiber's successful result #2104).
  • We reproduced the incorrect recovery callback with SQLite fault injection, but found no production reports of duplicate work caused by this exact bug.
  • Harm requires both a cleanup failure and a recovery hook that repeats work without checking whether it already happened. We expect this to be rare, but have no frequency data.

This affects legacy runFiber(), not the new Lifecycle/Tasks engine, which records completion before cleanup. Managed startFiber() runs with a saved terminal status are also protected. Think/AIChat still use legacy fibers for facet-hosted turns; top-level turns use Tasks.

History

Present since agents@0.10.0 (April 7, 2026), following #1256. #2244 preserves the successful result when cleanup fails; it did not introduce the underlying false recovery. This issue tracks the remaining problem separately from #2104.

Possible resolution

Record that the body finished so recovery can delete leftover rows without calling the hook. For facets, keep the parent's recovery registration until the child's row is deleted.

The existing cleanup-failure test checks the result and row cleanup, but should also check that the recovery hook was not called.

Alternatively, resolve this by safely retiring legacy fibers. Deprecation is contemplated, but remaining callers must migrate and existing runs must drain first. There is no committed removal date.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions