Files
zero/internal/cli/shutdown_test.go
KRATOSandKRATOS 99a0bea90b Real-world hardening: graceful shutdown + crash capture (#136)
* [LOCAL PR-Y] CLI: graceful shutdown of headless exec runs (Ctrl+C)

zero exec wrapped agent.Run in context.Background(), so Ctrl+C/SIGTERM killed the process abruptly — leaking the in-flight provider request and any spawned children. runExec now wraps the run in a signal-aware context (signalContext): a signal cancels the run, the loop returns cleanly, and exec reports 'Interrupted.' with exit 130 (stream-json: an 'interrupted' run-end). Scoped to runExec — no dispatch-wide signature churn. NOTE: SIGINT runtime delivery needs a manual Ctrl+C check before PR; unit tests cover the helper + the no-regression of the exec suite.

* [LOCAL PR-Y] Observability: crash capture (panic -> saved report)

New internal/observability package: a recovered top-level panic is written to a local crash report (timestamp, label, full stack) under ~/.zero/crashes and surfaced as a brief notice instead of a raw stack trace. Wired into runWithDeps via a named-return defer. Fail-open and dependency-free; this is the hook a Sentry/OTEL adapter attaches to for remote reporting (deferred, needs a real endpoint to verify). Tested: report formatting/writing, panic capture sets the crash exit code + writes one report, and the no-panic path is a no-op.

---------

Co-authored-by: KRATOS <[email protected]>
2026-06-08 08:55:36 +05:30

26 lines
544 B
Go

package cli
import (
"testing"
"time"
)
func TestSignalContextNotCancelledInitially(t *testing.T) {
ctx, stop := signalContext()
defer stop()
if ctx.Err() != nil {
t.Fatalf("signalContext should not be cancelled before a signal, got %v", ctx.Err())
}
}
func TestSignalContextStopCancels(t *testing.T) {
ctx, stop := signalContext()
stop()
select {
case <-ctx.Done():
// stop() releases the handler and cancels the context — expected.
case <-time.After(time.Second):
t.Fatal("stop() should cancel the signal context")
}
}