Files
command-center/agent/managed-skills/sigterm-kill-test-stub/SKILL.md
T

2.7 KiB

name, description
name description
sigterm-kill-test-stub Pin "code kills its child process" behavior with a bash stub player: make the stub SIGTERM-reactive via sleep&wait, log KILLED via trap; diagnose deferred-trap failures.

Use when a test must prove production code sends SIGTERM to a spawned child (player, worker, server stub) and the test asserts the kill happened — or when such a test mysteriously fails with KILLED missing and wall time equal to the stub's sleep duration.

The trap-during-sleep gotcha

A bash stub that sleeps in the foreground does NOT run its TERM trap when signalled: bash defers traps until the current foreground command completes. The stub dies (or logs) only after the sleep finishes — long after the test read the evidence file.

Symptoms:

  • Assertion on a KILLED marker line fails although production code demonstrably calls child.kill("SIGTERM").
  • Test wall time equals the stub's full sleep (the kill "had no effect").

Fix: reactive stub skeleton

#!/usr/bin/env bash
printf '%s\n' "$*" >> "$STUB_LOG"                       # call record
trap 'printf "KILLED\n" >> "$STUB_LOG"; exit 143' TERM  # BEFORE the sleep
for arg in "$@"; do case "$arg" in *.wav) cp "$arg" "$COPY" ;; esac; done
sleep "${STUB_HOLD:-0.1}" &                             # background sleep…
wait $!                                                  # …wait is interruptible
printf 'DONE\n' >> "$STUB_LOG"
  • sleep N & wait $! is the idiom: a signal makes builtin wait return immediately, the trap runs at once, exit inside the trap prevents fallthrough to DONE.
  • Tune duration per test via the env knob (STUB_HOLD=5) so the child is provably still alive when the kill is triggered.
  • Put the trap BEFORE any long command; a trap registered after the sleep never guards it.

Harness wiring (black-box driver pattern)

  • Scenario: start the long work → settle (>spawn latency, <hold) → trigger the killer (shutdown event, second invocation, /stop) → settle → read log.
  • Expose the stub log lines on the driver's stdout (LOG <line> per line) so tests grep output, not files: black-box seam stays output-only.
  • Assert KILLED present; for interrupt-semantics also count (kills -eq 1) and total call lines (PLAYS 3 = call, KILLED, call).

Orphan hygiene

Second child still sleeping when the driver exits: spawn with stdio: "ignore" so the orphan holds no capture pipe — it SIGPIPEs/quiets at teardown; do not lengthen tests waiting for it.

Verify before trusting

Run the pair separately: kill-tests green while a byte-equality test (cp of the payload before sleep) stays green — the reactive rewrite must not drop the copy step (it sits between trap and sleep).