Sanity Library Reference Docs
    Preparing search index...

    Completion is FIRST-WRITER-WINS: reporting on an entry that already settled throws EffectNotFoundError, whose settled field says how it settled — another party's completion (a lease takeover, a manual recovery), or an abort cancelling the entry before dispatch. A completer on a retrying transport (webhook redelivery, queue, cron) should pass DedupableOperationArgs.idempotencyKey — its own lost-response retries then replay as changed: false instead of throwing.

    interface CompleteEffectArgs {
        detail?: string;
        durationMs?: number;
        effectKey: string;
        error?: { message: string; stack?: string };
        grantsFromPath?: string;
        idempotencyKey?: string;
        instanceId: string;
        ops?: FieldOp[];
        outputs?: Record<string, unknown>;
        status: EffectCompletionStatus;
    }

    Hierarchy (View Summary)

    Index

    Properties

    detail?: string
    durationMs?: number
    effectKey: string

    The _key of the pending effect being reported on.

    error?: { message: string; stack?: string }
    grantsFromPath?: string

    URL path for grant resolution — see StartInstanceArgs.grantsFromPath.

    idempotencyKey?: string

    Caller-supplied key that makes this call safe to retry blind (a network failure ate the response; a queue redelivered the message). The first commit records the key on the instance's processedRequests[] ledger — in the same CAS write as the operation itself — and a retry carrying the same key replays instead of double-applying (or, for a completed effect, throwing EffectNotFoundError): the operation is NOT re-applied, but the cascade runs to convergence (the original may have died before its own cascade), so changed is false unless that cascade repaired something. Keys live for the engine's idempotencyTtlMs (default 24h); reusing a live key on a different verb throws. The caller owns key uniqueness per logical request.

    Only a commit records the key: a keyed call that no-ops (a setStage already at its target, an abort of a terminal instance) leaves no ledger row, so retrying it later re-evaluates fresh rather than replaying.

    instanceId: string
    ops?: FieldOp[]

    The state half of the effect: field.* ops to apply in the completion commit, computed from the effect's real result (e.g. a field.set of a created doc's ref, or the screened outcome an activity/stage gate then reads). Run through the same op applier as an action's field ops — a target must resolve in the current open stage and its value must match the field's kind; workflow- and stage-scope targets only. Deliberately NOT status.set: an effect is outside the activity's awaiting, so it reports its result as field state and lets the gate decide the activity/stage outcome — it never flips an activity status itself (same boundary a transition draws). Only valid with status: 'done' (a failed completion carries no ops). Not actor-gated.

    outputs?: Record<string, unknown>

    Named values produced by the effect — recorded on the run's effectHistory row and read downstream as $effects['<effect name>'].<output> (the latest completed run per effect name wins). Validated against the effect's declared outputs allowlist; each value may be any JSON.