Failure Modes
What breaks, how it presents, and what to do — keeper, RPC, transactions, execution, risk halts.
Keeper offline / crashed
Presentation: tape stops growing; no copy transactions; deposits/withdrawals still work. Action: restart the process. No backfill occurs — missed fills are simply not copied. Investigate the crash log before restarting in a loop.
RPC failure
Presentation: keeper log error streaks; reads revert; dry-runs fail.
Action: fail over by updating RH_RPC_URL / RH_TESTNET_RPC_URL (and the NEXT_PUBLIC_ twins for the app) and restarting. The keeper has no multi-RPC fallback built in.
Transaction failure (live mode)
Presentation: eth_sendTransaction errors or on-chain reverts in the keeper log.
Likely causes, in order:
- Router unset —
SwapAdapter.router == address(0)⇒ everyexecuteCopyreverts. Expected on 46630; a misconfiguration on 4663. - Risk rejection raced on-chain — caps/session/drawdown re-evaluated at execution; the copy reverts rather than executes oversized. This is the system working.
- Insufficient cash — copy notional exceeds vault cash.
- Gas/nonce issues — standard key management; the keeper submits from
KEEPER_ADDRESSwith the configuredPRIVATE_KEY.
Action: read the revert reason, fix the cause, let the next fill trigger a fresh decision. Do not manually replay old calldata — decisions are point-in-time.
Stuck execution
There is no keeper-side queue to get stuck. "Stuck" usually means the withdrawal queue is growing because the desk is fully invested (mainnet, future) or paused. Queue processing is permissionless — anyone can call processWithdrawals; the keeper is not required.
Risk halt (drawdown)
Presentation: every copy rejected with the drawdown reason; cast shows NAV/share ≥ 20% (default) below the high-water mark.
Action: this is intended behavior. The halt lifts only when NAV/share recovers above the threshold. Do not "fix" it by raising MAX_DRAWDOWN_BPS mid-drawdown — that defeats the control. Communicate to depositors; evaluate the leader.
Oracle staleness / bad price
Presentation: copies rejected for staleness (default max 120 s) or oracle BadPrice reverts.
Action: check the Chainlink feed on-chain. The system fails closed — no price, no trade. Never point feedOf at an unverified feed to force trades through.
Fill-source garbage (live indexer)
Presentation: live fills rejected as ZERO_NOTIONAL.
Explanation: the shipped LiveFillSource decodes direction only; notional/price are zero until a cited price source is attached. This is a known limitation, not a bug to hot-patch. Use the fixture source or attach a real price source before expecting live copies.