Skip to content

environments work poller: ack-before-yield makes any consumer failure permanently strand the work item — request deferred ack or a nack/fail API #1746

Description

@arcaputo3

anthropic.lib.environments._poller acknowledges every work item before yielding it to the consumer (_poller.py:234; docstring at :66: "Each yielded BetaSelfHostedWork has already been ack'd"). There is no deferred-ack option and no nack/fail API. Verified identical from 0.109.1 through 0.116.0.

For self-hosted managed-agents environments this makes any consumer-side exception after the yield permanently strand that session:

  • the acked item freezes in state='starting' with latest_heartbeat_at=null;
  • reclaim_older_than_ms cannot reach it (it only reaches unacknowledged items), and lease-TTL re-queue never fires (requires a first heartbeat);
  • the platform keeps one work item per session, so all subsequent user.message events park in requires_action with no new work enqueued — the session is wedged until an operator runs work.stop(work_id, force=True) and sends a user.interrupt.

We hit this in production twice in one day (different consumer exceptions, same terminal state) and separately counted 29 items stuck in starting with null heartbeats.

Ask

Either of:

  1. Deferred ack: an opt-in poller mode that acks after the consumer callback returns successfully (at-least-once semantics), or
  2. nack/fail API: a way for a consumer to return a failed item to a re-pendable state.

Related, happy to file separately: documenting which work-item states re-enqueue on which user events (empirically: stopped re-pends on message/interrupt; a dead-heartbeat started item only after a server-side stop ~5 min later; an acked-but-never-heartbeated starting item never), and a server-side janitor for starting + null-heartbeat items older than a TTL.

Workaround we ship today

Consumer retry + work.stop(force=True) on final failure, plus a scheduled sweep that force-stops stale starting items and interrupts sessions parked in requires_action with no live worker. Effective, but every self-hosted-environment consumer will independently rediscover this failure class — ack-after-success semantics in the SDK would eliminate it.

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