fix(client): detach gracefully on controlling-terminal hangup
A terminal hangup or termination signal killed the attach client with the default disposition, skipping transport close and the performance-trace flush. Bind the attach context to SIGHUP/SIGTERM/SIGINT so teardown unwinds the pumps, closes the transport, and pairs the in-flight receive span. Raw mode disables ISIG, so interactive Ctrl+C is unaffected.
- commit
- 05e5006fe6c63262c6b74c759c9fa5552626cadf
- author
- brice · · 2026-07-18 16:28 UTC
- committer
- brice · · 2026-07-18 16:35 UTC
- parent
- 156ea64
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +9 -0
M internal/app/run.go +9 -0
| @@ -480,6 +480,15 @@ func runDaemon() (retErr error) { | |||
| 480 | 480 | // attach loop. Logging goes to the shared file: the client must never write | |
| 481 | 481 | // to the console while the terminal is raw. | |
| 482 | 482 | func runAttach(ctx context.Context, intent uint8, name, remoteTarget string) (retErr error) { | |
| 483 | + | // Treat a controlling-terminal hangup or termination request as a graceful | |
| 484 | + | // detach rather than an abrupt process death. Cancelling the context unwinds | |
| 485 | + | // the client's pumps, closes its transport, and lets the deferred trace closer | |
| 486 | + | // flush the in-flight receive's end mark — so teardown never truncates a span. | |
| 487 | + | // Raw mode disables ISIG, so catching SIGINT here does not affect interactive | |
| 488 | + | // Ctrl+C, which the daemon delivers to the remote shell as a normal keystroke. | |
| 489 | + | ctx, stop := signal.NotifyContext(ctx, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT) | |
| 490 | + | defer stop() | |
| 491 | + | ||
| 483 | 492 | log, logCloser, err := configureLogging(logging.Client, false) | |
| 484 | 493 | if err != nil { | |
| 485 | 494 | return err | |
| @@ -480,6 +480,15 @@ func runDaemon() (retErr error) { | |||||
| 480 | // attach loop. Logging goes to the shared file: the client must never write | 480 | // attach loop. Logging goes to the shared file: the client must never write | ||
| 481 | // to the console while the terminal is raw. | 481 | // to the console while the terminal is raw. | ||
| 482 | func runAttach(ctx context.Context, intent uint8, name, remoteTarget string) (retErr error) { | 482 | func runAttach(ctx context.Context, intent uint8, name, remoteTarget string) (retErr error) { | ||
| 483 | + | // Treat a controlling-terminal hangup or termination request as a graceful | |||
| 484 | + | // detach rather than an abrupt process death. Cancelling the context unwinds | |||
| 485 | + | // the client's pumps, closes its transport, and lets the deferred trace closer | |||
| 486 | + | // flush the in-flight receive's end mark — so teardown never truncates a span. | |||
| 487 | + | // Raw mode disables ISIG, so catching SIGINT here does not affect interactive | |||
| 488 | + | // Ctrl+C, which the daemon delivers to the remote shell as a normal keystroke. | |||
| 489 | + | ctx, stop := signal.NotifyContext(ctx, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT) | |||
| 490 | + | defer stop() | |||
| 491 | + | ||||
| 483 | log, logCloser, err := configureLogging(logging.Client, false) | 492 | log, logCloser, err := configureLogging(logging.Client, false) | ||
| 484 | if err != nil { | 493 | if err != nil { | ||
| 485 | return err | 494 | return err | ||