internal/notify/notify.go:97 uses http.NewRequest rather than NewRequestWithContext. The webhook ignores operation cancellation; only the 5s client timeout bounds it. Line 85 also assigns body, contentType := []byte(nil), "application/json" and then reassigns both in each branch.
internal/transport/ssh.go:252 type-asserts err.(*ssh.ExitError) instead of using errors.As. A wrapped exit error would be reported as a transport failure rather than surfacing its exit code — which matters here, because several call sites branch on res.ExitCode for expected non-zero statuses. Same shape at cmd/ob/preview.go:135 for *app.Error.
%v where %w is intended, so callers cannot errors.Is through: internal/engine/bootstrap.go:60, internal/engine/gate.go:260, internal/engine/lock.go:79.
cmd/ob-scheduled-runner/main.go:77 calls os.Exit(130), skipping defer stop().
Split out of #50 as item 9.
internal/notify/notify.go:97useshttp.NewRequestrather thanNewRequestWithContext. The webhook ignores operation cancellation; only the 5s client timeout bounds it. Line 85 also assignsbody, contentType := []byte(nil), "application/json"and then reassigns both in each branch.internal/transport/ssh.go:252type-assertserr.(*ssh.ExitError)instead of usingerrors.As. A wrapped exit error would be reported as a transport failure rather than surfacing its exit code — which matters here, because several call sites branch onres.ExitCodefor expected non-zero statuses. Same shape atcmd/ob/preview.go:135for*app.Error.%vwhere%wis intended, so callers cannoterrors.Isthrough:internal/engine/bootstrap.go:60,internal/engine/gate.go:260,internal/engine/lock.go:79.cmd/ob-scheduled-runner/main.go:77callsos.Exit(130), skippingdefer stop().Split out of #50 as item 9.