> ## Documentation Index
> Fetch the complete documentation index at: https://ctrlrun.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Resolve an AMBIGUOUS effect

> Find the effects nobody knows the outcome of with ctrlrun effects, ask the remote what happened, say so with ctrlrun resolve --committed or --failed.

An `AMBIGUOUS` effect is one whose executor raised something other than `NotExecuted`, timed
out, or never returned. CTRLRun will not guess, so a person asks the remote and records the
answer with `ctrlrun resolve`. This guide makes one, finds it, resolves it both ways, and shows
what the evidence says afterwards.

**Prerequisites:** `pip install ctrlrun`, an empty directory. The remote is a stand-in that
commits and then loses the reply.

<Steps>
  <Step title="Make an ambiguous effect">
    ```yaml runnable theme={null}
    schema: ctrlrun.policy/v2

    actions:
      stripe.refund:
        effect: "refund:{payment_id}"
        decision: allow
      dns.update_record:
        effect: "dns:{zone}:{name}"
        decision: allow
    ```

    ```python runnable theme={null}
    import ctrlrun


    @ctrlrun.protect("stripe.refund", effect="refund:{payment_id}")
    def refund(payment_id: str, amount: int) -> dict:
        raise TimeoutError("no response from api.stripe.com after 30s")  # after the money moved


    @ctrlrun.protect("dns.update_record", effect="dns:{zone}:{name}")
    def update_record(zone: str, name: str, value: str) -> dict:
        raise ConnectionResetError("connection reset by peer")


    with ctrlrun.context(agent="ops-agent"):
        for call in (
            lambda: refund(payment_id="txn_7", amount=50000),
            lambda: update_record(zone="example.com", name="api", value="203.0.113.7"),
        ):
            try:
                call()
            except (TimeoutError, ConnectionResetError) as lost:
                print("the executor saw:", lost)
    ```
  </Step>

  <Step title="Find it">
    ```bash runnable theme={null}
    ctrlrun effects --state ambiguous
    ```

    ```text theme={null}
    refund:txn_7          ambiguous  …
    dns:example.com:api   ambiguous  …
    ```

    A retry of either is refused with `AmbiguousEffect` until the record moves. A worker that
    died mid-call ends here too, when its lease lapses; nothing sweeps and nothing releases.
  </Step>

  <Step title="Ask the remote, then say what happened">
    Look in the Stripe dashboard, or query the DNS zone. Then record the answer. Exactly one of
    the two flags, and only for an effect that is `AMBIGUOUS`:

    ```bash runnable theme={null}
    ctrlrun resolve refund:txn_7 --committed
    ctrlrun resolve dns:example.com:api --failed
    ctrlrun effects
    ```

    `--committed` means the remote did it: a retry is now refused as a duplicate.
    `--failed` means it provably did not: a retry is now permitted. Neither undoes anything.
  </Step>

  <Step title="Read who resolved it">
    The resolution is an `EFFECT_RESOLVED` event carrying `resolved_by`, and
    `ctrlrun inspect <action_id>` shows it in the action's history:

    ```bash runnable theme={null}
    ctrlrun inspect "$(ctrlrun receipts --last 2 --json | head -1 | python -c 'import json,sys; print(json.loads(sys.stdin.readline())["action_id"])')"
    ```

    A retry after a resolution is a new action with its own receipt; it is never attributed to
    the resolver.
  </Step>
</Steps>

## When to reach for a hook instead

If the remote can be asked programmatically, a `reconcile` hook asks it for you and moves the
record the same way, with the same rule that it moves only in the direction the answer points:
[Reconcile automatically](/guides/reconcile-automatically).

## If it didn't work

* `resolve` exits 1 with `is not ambiguous`: the effect already has a known outcome. A committed
  effect cannot be moved back to failed, or the other way.
* `resolve` exits 1 with `nobody reserved`: the key is misspelled, or the effect belongs to
  another store; check `--store-url` or `$CTRLRUN_STATE`.
* `exactly one of --committed and --failed`: pass one flag.

## Next

* [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous): why there is a third outcome.
* [Reconcile automatically](/guides/reconcile-automatically).
* [Get started](/get-started/quickstart) · [Why](/why).


## Related topics

- [Resolve an ambiguous effect](/cookbook/resolve-an-ambiguous-effect.md)
- [Outcomes and AMBIGUOUS](/concepts/outcomes-and-ambiguous.md)
- [CLI reference](/reference/cli.md)
- [Reconcile automatically](/guides/reconcile-automatically.md)
- [A credential-rotation agent](/cookbook/credential-rotation-agent.md)
