Runbook: incidents
This runbook is for the account owner. It says what to do when something in sesame-bun looks wrong while trading. Each section starts with the steps to take now, then how to find out what happened. Related: user guide (what the controls and reject messages mean), Session Key setup (rotation and revocation), and the secrets and logging policy (compromise response). Where behaviour is not fixed yet it says defined by implementation.First steps for any incident
When you are not sure what is happening, do these in order. Each one is safe on its own.- Cancel All:
Mod+Shift+X(Ctrl+Shift+X on Windows and Linux), or the top-bar Cancel All. It works in SAFE and is never blocked by the risk engine. - Switch to SAFE with the header switch.
- Check polymarket.com for open orders and recent trades on the account. It is the reference when the app and the venue disagree.
- If the app itself may be at fault, set
LIVE_TRADING=falseand restart. The app starts in SAFE and with trading off; positions, orders and cancels still work.
.env.
1. A key may have leaked
Treat the Session Key as leaked if its private key, or the.env holding it, appeared
anywhere outside the app host: a log, a commit, a screenshot, a chat, an email, a ticket
or an agent session; or if the host may be compromised.
A leaked Session Key may be able to move funds out of the Deposit Wallet: the wallet
contract does not stop it, only Polymarket’s relayer does
(ADR 0012). Even if it cannot, it can lose them by
trading: it can sell your positions into an attacker’s low bids or buy an attacker’s high
asks (security finding F5). That is why the wallet holds only the working balance set in
ADR 0013, and why the key is revoked at once.
Now:
- Cancel All, switch to SAFE, set
LIVE_TRADING=falseand restart the app. - Revoke the Session Key from the owner machine (Session Key setup §6). This stops the key from trading and cancels its open orders. Revoking only the CLOB API credentials is not enough: the key can derive new ones.
- On polymarket.com, check positions and history for trades you did not make.
- Rotate every secret the host held: create a new Session Key
(Session Key setup §5), a new UI password hash
(
task hash-password), a newSESSION_SECRET, and the Telegram bot token if set. Clear anyPOLYMARKET_CLOB_*values; they belonged to the old key. - If the host may be compromised, rebuild it before putting the new key on it.
- Compare the audit log with Polymarket’s history (
task audit-verify, then the comparison in live verification step 15). Trades in Polymarket’s history that are not in the audit log were not made by the app. - If the leaked key’s scope turns out to be broader than
CLOB, move the funds with the owner key on polymarket.com. - Record the date and reason of the rotation.
failed with auth_rejected; when
exactly is defined by implementation. How fast revocation takes effect at the venue is
UNVERIFIED (secrets policy §5).
2. Fills or orders you did not make
The app compares the account’s activity on Polymarket (from the Data API, which sees every entry on the wallet) with its own audit log. It reports as foreign activity:- an order, fill or position change it did not place;
- a withdrawal or other outgoing pUSD transfer, a split, a merge or a conversion. The app never does any of these, so each one is either you on polymarket.com or someone holding the key (security finding P3R-02).
GET /api/trading reasons shows foreign_activity
(security finding F5). The flag is stored, so a restart does not clear it.
Now:
- Cancel All. The app is already SAFE; keep it there.
- Ask yourself whether the activity was yours: a trade, withdrawal, transfer, split, merge or conversion on polymarket.com, on the mobile app, or with another tool or Session Key. A Session Key sees only its own orders, so trading from anywhere else on the same wallet looks foreign to the app. Compare the entry’s time and amount in polymarket.com’s history with what you did.
password in the body of
POST /api/trading/arm (HTTP 403 step_up_required without it, 429 rate_limited after
too many attempts). A correct password arms and clears foreign_activity. A stolen
browser session alone cannot re-arm. To avoid the pause, do not trade or move funds on
the same wallet from elsewhere while the app runs.
If it was not yours: treat the key as leaked and follow section 1.
If the Session Key was not the source (for example, the trades came from the owner’s
own login), secure the owner account on polymarket.com as well.
Unexpected fills on your own orders are not foreign activity: an order you left
resting can fill later, including a stale order after a price jump. Check Orders and the
fills list, and the audit log for the order’s client_order_id.
A combo trade the app did not make has its own section:
section 7.
3. The venue is in cancel-only or post-only mode
Polymarket sometimes restricts trading. The app shows the reason in reject toasts and, for cancel-only, a status pill. The app never retries an order on its own.
Cancels, Cancel All and the Exit @ join order keep working in post-only mode. In
cancel-only mode cancels still work. How long each mode lasts is up to the venue; the app
has no setting that changes it.
4. An order stuck in unknown
An order is unknown when the venue did not answer in time: it may or may not be on the
book. Before sending, the app recorded the signed order’s id (its order hash) in the
audit log (ADR 0011). It never re-sends the
order; it reconciles by that id: the open orders holding it, a fill of it on the user
feed, or a lookup of it at the venue. The chip shows ? (Checking…) until
reconciliation settles it.
Unresolved placements. GET /api/trading unresolved_placements counts the
placements still unknown (security finding P3R-07). The app looks each one up 2 to 30
seconds after the unknown outcome; a count still above 0 after that means the venue has
not answered the lookup, and the app asks again every 60 seconds until it does. While a
buy is unresolved, its notional stays reserved against max_position_notional, so new
buys in that market can be refused. An id the venue answers as not found is settled as
not placed. The count also survives a restart: at start, the app looks up every placement
the audit log left without a final outcome.
Now:
- Do not click again. A new click is a new order.
- Wait for the chip to become
placed, a fill, orNot placed. Reconciliation normally settles it within 30 seconds.
unresolved_placements stays above 0):
- Check polymarket.com for the order at that market, side and price.
- If it is on the book and you do not want it, cancel it in the app (cancel-market covers it even when the app has no order id for it) or on polymarket.com.
- Check the user feed: Settings or
GET /api/statusshould showpm.userconnected. While it is down, order updates pause and the orders views are greyed. - Restart the app. At startup it reads the open orders from the venue and looks up each unresolved placement by its order id. It starts in SAFE.
- Look up the
client_order_idin the audit log: it has the intent, decision andsignedrows (the signed row holds the order id), a response row recordingunknownand, once settled, areconcilerow with what was found. Report a case that never settles, with theclient_order_id, the order id and the log lines (order outcome unknown), to the tech lead.
5. Other problems
6. A combo Accept stuck in Checking…
An Accept is unknown when Polymarket’s answer did not arrive or could not be read: a
timeout, a lost connection, a 5xx (503 included) or a 409 INVALID_RFQ_STATE. The combo may
or may not have been bought. Before sending, the app recorded the signed order’s hash, the
rfq_id and the quote_id in the audit log. It never sends the Accept again; it reads
the quote request’s status at Polymarket by its rfq_id
(ADR 0015). The slip reads Checking…, then
Checking combo · no response after 10s.
The app reads the status every 500 ms, every 5 s once the Accept has run for a minute, and
backs off up to a minute between failed reads. It settles the Accept as:
- filled (
CONFIRMEDorFILLEDwith a transaction hash):Combo filled; - not filled (
FAILED,CANCELED,EXPIRED,REJECTEDor a status the app does not know):Combo rejectedorCombo failed; - never recorded (Polymarket answers that it holds no Accept for the quote):
Rejected · never reached the venue(venue_not_found).
max_combo_exposure (a sell’s shares against the position), the leg toggles and Exit are
disabled (Combo being accepted), and GET /api/combos/rfqs lists the quote request with
status unknown or executing.
Now:
- Do not get a new quote and accept it. The first Accept may still fill, and a second one would buy a second combo.
- Wait for the slip to read
Filled,Rejected · …or theCombo filled,Combo rejectedorCombo failednotification. Cancel all does not affect an Accept already sent.
- On polymarket.com, check whether a combo position or combo trade appeared at the time of the Accept.
- In the log, look for
combo accept outcome unknownwith itsrfqId, and repeatedcombo status read failedlines: the status reads are failing, not the Accept. - Restart the app. At start it resumes the status reads of every Accept the audit log left
without a final outcome, with its reservation held, and logs
reconciling combo acceptswith the count. It starts in Safe. - Look up the Accept’s
client_order_idin the audit log: the intent, decision andsignedrows (rfq_id,quote_id,order_hash), aresponserow withunknownorexecuting, and, once settled, areconcilerow with the status andtx_hash. Report a case that never settles, with theclient_order_id, therfq_idand those log lines, to the tech lead.
7. A combo trade the app did not make
The app explains a combo trade in the account’s activity (a/v2/activity trade marked
combo) only by one of its own filled Accepts with the same transaction hash (PLAN §3.7).
Any other combo trade is foreign activity, handled as in section 2:
an alert, Safe, and foreign_activity in GET /api/trading reasons. The cash it moved
is not explained either, since combo trade cash is counted only from the app’s filled
Accepts, so a cash alert can follow. While one of the app’s own Accepts is still being
checked (section 6), an unmatched combo trade waits for the next check instead of being
reported at once.
Now:
- Cancel all. The app is already Safe; keep it there.
- Ask whether it was yours: a combo bought or sold on polymarket.com or with another tool counts as foreign, like any trade made outside the app. Compare the trade’s time and amount in polymarket.com’s history with what you did.
venue_not_found, Combo rejected or Combo failed, with a trade at the same time on the same legs): the status
read and the trade disagree. Security finding P8-01 describes one way this happens: a
first status read that answers 409 before Polymarket has recorded the Accept. Stay Safe and
report it to the tech lead with the Accept’s client_order_id, its rfq_id, the trade’s
transaction hash and the log lines.
If it is a combo the app filled (Combo filled with the same transaction hash) and the
alert still came, or it came from the order manager rather than the activity check: the
combo fill reached the app through the CLOB user feed or the fills backfill (security
finding P8-04). Stay Safe and report it the same way.
While an Accept of the app stays in Checking…, unmatched combo trades wait for it to
settle and are not reported (security finding P8-03). If one stays in Checking… for more
than a few minutes, check polymarket.com’s history for combo trades you did not make
(section 6).
8. Combos unavailable
combos_unavailable has two causes.
The Session Key lacks the combos scope. The Combo toggle and Get quote read
Combos unavailable · Session Key lacks the combo scope, GET /api/trading
combos_reasons lists combos_unavailable, and a quote request is refused with it
before anything is sent. GET /api/account shows credentials.scopes without combos.
Predictions trading and the combo positions are not affected.
- Check the start log’s
session key confirmedline: itsscopeslists what Polymarket reports for the key. - Issue a key with
CLOBandCOMBOSRFQ(orALL), switch to it and revoke the old one (Session Key setup §8.2). The scopes are read when the credentials start, so the change needs a restart.
ready, combos report no_credentials instead; see the
credential state in Settings (Session Key setup §7).
No Combos venue. The combo endpoints answer HTTP 503 combos_unavailable:
POST /api/combos/quote, /api/combos/accept and GET /api/combos/rfqs when the app runs
without POLYMARKET_SESSION_KEY, and the combo positions and activity endpoints when it
also runs without POLYMARKET_WALLET_ADDRESS. Set the variables
(Session Key setup §3) and restart.