---
title: Fix the auto-nomination that lands after the mode flips to election
type: task
status: planned
description: >-
  A live-mode auto-nomination can complete after the mode poll has already left live mode, so the
  nomination lands in election mode and its bid timer is suspended, leaving the player open with no
  clock. Self-healing but reachable during the final block.
base_uri: user:task/league/fix-auction-nomination-mode-flip-interleaving.md
created_at: '2026-09-07T18:54:15.092Z'
entity_id: 517644a7-3723-46e6-a3d6-93c0507236b4
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-10-05T15:32:48.140Z'
visibility_analyzed_at: '2026-11-05T23:49:18.380Z'
---

## Problem

A live-mode auto-nomination can land after the mode has already flipped to election, leaving the nominated player open with no bid clock.

Observed in production on league 119, 2026-09-07:

```
18:31:57.809  auction:league:119  auto-nominating JARE-GOFF-002009 for team 331
18:31:57.844  auction:league:119  leaving live mode
18:31:57.848  auction:league:119  received nomination for JARE-GOFF-002009 for $0
18:31:57.894  auction:league:119  bid timer suspended in election mode
```

The sequence is a race between two periodic signals on the socket. The nomination timer expires and `_auto_nominate` starts; while it is awaiting its several reads, the 60-second mode poll resolves election and calls `_leave_live_mode`. That method's "finish under live clocks" deferral checks whether the latest transaction is an `AUCTION_BID`, and at that instant the nomination has not been written yet — so it sees no open player and reverts cleanly. The nomination then lands into election mode, where `nominate` takes the election branch and `_start_bid_timer` refuses.

## Severity

Low, and it self-heals. On the next transition to live, `_enter_live_mode` sees the latest transaction is an `AUCTION_BID`, re-applies proxy bids and starts a bid timer, so the player is picked back up on the next live transition. In election mode the player is not stranded either — it settles when the eligible set completes.

**"One or two poll intervals" was the original estimate and it is wrong — measured at 204 seconds on league 119 on 2026-09-07.** The recovery is bounded by how long the mode stays election, not by the poll period, and during the final block those are not the same thing. Each signing pushes `final_block_at` 120 seconds later while the auction places a player in about 60 seconds, so a burst of signings banks a debt of election mode that is only paid back when the wall clock catches up. Two signings 21 seconds apart moved the boundary from 21:42 to 21:46. This does not change the Low severity — it still self-heals unaided, and the board was never stranded — but a fix scored against a two-minute expectation would be scored against the wrong number.

The window is roughly 50ms against a 45-second nomination timer, so the per-nomination probability is small. It is not vanishing, though: the nomination timer and the mode poll are both periodic with close periods, so they beat against each other and the alignments are not independent draws.

It matters most during the mandatory final block, where the mode oscillates every couple of minutes by design — each signing pushes `final_block_at` later than `now` — so live/election transitions are frequent rather than rare.

## Deliberately not fixed on 2026-09-07

Found hours before league 1's mandatory final block, inside a deploy window. The candidate fixes all touch the ordering between `_auto_nominate`, `nominate` and `_leave_live_mode`, which is the surface that concludes a player under a clock. Getting that wrong is far worse than a self-healing two-minute delay, so it was recorded rather than patched.

## Plan

- [ ] Reproduce it deterministically in a spec by driving `_refresh_mode` into election between the start of `_auto_nominate` and the nomination write, rather than by timing. Verify: the spec fails on today's code and names the suspended bid timer.
- [ ] Decide the fix. The two candidates are re-checking the mode inside `nominate` before choosing the election branch, and having `_leave_live_mode` defer while an auto-nomination is in flight. Verify: whichever is chosen, state why the other was rejected in the code comment.
- [ ] Implement, and score it by deleting the fix and requiring the spec red while an unperturbed control stays green.
- [ ] Confirm the self-heal path still works, so the fix does not remove the recovery it is standing in front of. Verify: a player nominated into election mode still gets a bid clock on the next `_enter_live_mode`.
- [ ] Settle the `_pending_election_mode` stale-latch finding, which produces the same symptom by a second route and is so far only read, never observed. `_leave_live_mode` sets the latch at `auction.mjs:2491` without setting `_election_mode`, so when the resolver returns to live the guard at `auction.mjs:2168` finds `should_be_election === this._election_mode` and returns early — skipping `_enter_live_mode`, the only writer that clears the latch (`:2340`). `sold()` at `:580` then acts on it and flips to election while the resolver says live. Verify: a spec drives that exact poll sequence and fails on today's code; if it cannot be made to fail, say so and strike this rather than fixing a path that does not exist.

## Related

- `██████████████████████████████████████████████████████`
- `repository/active/league/docs/guides/auction.md` — the final-block and mode-resolution sections, which document the oscillation this rides on.
