From e48073397f9d3b16e7c6a75a7d8144227f17b576 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Sat, 7 Mar 2026 10:25:51 -0500 Subject: [PATCH] fix(state-machine): add 2-minute safety timeout to dispensingCash state If the dispenseCash promise hangs (hardware jam, serial port freeze, waitForBillsRemoved stuck), the machine was trapped in dispensingCash forever with no way to recover. Now it transitions to dispenseError after 2 minutes, which then auto-returns to idle after 30 seconds. This ensures the ATM always recovers to a usable state, even when hardware fails mid-dispense. Co-Authored-By: Claude Opus 4.6 --- packages/state-machine/src/machine.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/state-machine/src/machine.ts b/packages/state-machine/src/machine.ts index 02e31e9..5f0919a 100644 --- a/packages/state-machine/src/machine.ts +++ b/packages/state-machine/src/machine.ts @@ -392,6 +392,7 @@ export function createATMMachine( delays: { TIMEOUT_MS: 300000, // 5 minutes COMPLETE_DELAY: 60000, + DISPENSE_TIMEOUT: 120000, // 2 min max for hardware to respond DISPENSE_ERROR_TIMEOUT: 30000, // 30s like brain.js _timedState }, }).createMachine({ @@ -654,6 +655,16 @@ export function createATMMachine( }, }, dispensingCash: { + // Safety timeout: if dispenseCash promise hangs (hardware jam, + // serial port freeze), don't stay here forever. + after: { + DISPENSE_TIMEOUT: { + target: 'dispenseError', + actions: assign({ + error: () => 'Dispense timed out — hardware may be jammed', + }), + }, + }, invoke: { src: 'dispenseCash', input: ({ context }) => context.dispenseAmounts,