Why not just retry?
- Retry blindly and you risk doing it twice: two charges, two issues, two emails.
- Give up and you risk leaving it half-done.
What Invoke does
1
The action is marked unknown
A timeout, a dropped connection, or an error after the tool accepted the request all count. The reserved budget stays held, since the money may have been spent.
2
Safe-to-repeat tools are reconciled
Reads, and tools that converge by construction such as
linear.update_issue (read first, fixed ids), are safe to run again as the same action. The SDK re-sends the same action, Invoke re-runs it as the same effect, and it lands at most once. You get the receipt.3
Everything else waits for a person
For a charge, an email or a new record, running again could act twice. So Invoke refuses to re-run it: the call throws
OutcomeUnknownError with safeToRetry: false, and every retry with the same key is refused too. Someone checks the real system and settles it:happened commits the action and seals its receipt. not_happened releases the budget, so an honest retry can run.In code
Checking live state yourself
If you already hold the live state, such as a Stripe lookup you ran,POST /outcomes/reconcile tells you whether a retry is safe:
Next steps
- Payments: unknown outcomes on charges
- TypeScript SDK: every error and what it means
- POST /outcomes/reconcile: the full API reference