fix(managed-server): snapshot capture and restore run on BusyBox before 1.30 (#30840)

The state mutation wrapper ran `timeout -s KILL 900 sh -c ...`, which old
BusyBox (`timeout [-t SECS] [-s SIG] PROG`) reads as running a program
named 900. The script never ran, the capture answered nothing and parsed
as failed, so every update stopped the server and rolled back. Probe the
syntax as the room check (#30618) already does.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Neil
2026-10-19 12:52:55 -07:00
committed by GitHub
co-authored by Claude
parent 9ab194fef6
commit 2b07f0f3b6
2 changed files with 23 additions and 1 deletions
@@ -1,4 +1,5 @@
import {
chmodSync,
existsSync,
mkdirSync,
mkdtempSync,
@@ -115,6 +116,25 @@ describe.skipIf(process.platform === 'win32')('snapshot commands on a real shell
expect(existsSync(join(root, '.orcad-state-restore-stage'))).toBe(false)
})
it('captures on a BusyBox before 1.30, whose timeout only takes `-t SECS`', async () => {
const bin = join(base, 'bin')
mkdirSync(bin)
// Old BusyBox reads a bare `900` as the program to run.
writeFileSync(
join(bin, 'timeout'),
'#!/bin/sh\n[ "$1" = "-t" ] || exit 127\nshift 2\n[ "$1" = "-s" ] && shift 2\nexec "$@"\n'
)
chmodSync(join(bin, 'timeout'), 0o755)
expect(
parseOrcadSnapshotCapture(
await sh(captureOrcadStateSnapshotCommand(posix, root, snapshot, remoteBase), {
PATH: `${bin}:${process.env.PATH ?? ''}`
})
)
).toBe('captured')
})
it('keeps live state when the archive cannot be extracted', async () => {
await sh(captureOrcadStateSnapshotCommand(posix, root, snapshot, remoteBase))
writeFileSync(join(snapshot, 'state.tar'), 'not a tar archive')
@@ -135,7 +135,9 @@ export function serializedStateMutationCommand(
'else "$@"; fi; };',
// Why timeout inside the group: KILL then reaches tar and rm, not only the shell.
// The deadline is a word list, so the script is sent once, not once per branch.
`orca_deadline=; command -v timeout >/dev/null 2>&1 && orca_deadline="timeout -s KILL ${ORCAD_STATE_MUTATION_DEADLINE_SECONDS}";`,
// BusyBox before 1.30 spells it `timeout -t SECS`, as the room probe found.
`orca_deadline=; if timeout 1 true 2>/dev/null; then orca_deadline="timeout -s KILL ${ORCAD_STATE_MUTATION_DEADLINE_SECONDS}";`,
`elif timeout -t 1 true 2>/dev/null; then orca_deadline="timeout -t ${ORCAD_STATE_MUTATION_DEADLINE_SECONDS} -s KILL"; fi;`,
`orca_state_group $orca_deadline ${run};`,
`status=$?; ${owned ? `[ "$status" -eq ${ORCAD_FENCE_LOST_EXIT} ] && exit ${ORCAD_FENCE_LOST_EXIT}; ` : ''}if [ "$status" -eq 124 ] || [ "$status" -eq 137 ]; then echo ${ORCAD_STATE_MUTATION_DEADLINE}; fi`
].join(' ')