Skip to content

feat: chain the caller's EXIT trap into server teardown - #26

Merged
Martin Bens (SpiGAndromeda) merged 3 commits into
mainfrom
feat/exit-trap-chaining
Sep 15, 2026
Merged

Martin Bens (SpiGAndromeda) merged 3 commits into
mainfrom
feat/exit-trap-chaining

Conversation

@SpiGAndromeda

@SpiGAndromeda Martin Bens (SpiGAndromeda) commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #25.

run_mcp_server replaced any EXIT trap a consumer had installed, so a server script's own cleanup never ran in the direct-call shape. The server now captures the handler it displaces and _server_teardown runs it after the SDK's own cleanup, on a clean exit and on a trapped signal alike. A subshell-isolated call keeps its old behavior: the parent's trap stays the parent's.

The handler's contract, documented in README §Cancelling and shutting down:

  • stdout goes to stderr (stdout is the protocol channel); stdin is /dev/null
  • runs without errexit; a failure is logged and changes neither the teardown nor the exit status
  • gets the same two-second grace a cancel hook gets; an overrun is killed and logged
  • runs before run_mcp_server returns on a clean exit
  • only a handler installed in the shell that calls run_mcp_server directly is chained

Also in this change: a posix-mode unset trap (trap -- - EXIT) is never eval'd as a handler; a signal recorded during any teardown pass is re-raised at the pass's end, so the shell dies by the signal on the clean-exit route too; and the README states the subshell wrap's cost precisely — a pid-only signal loses the SDK teardown, while the parent's EXIT trap still runs.

run_mcp_server captures the EXIT handler it displaces and _server_teardown
runs it after the SDK's own cleanup, on clean exit and trapped signals alike.
Direct-call shape only; a subshell-isolated call keeps today's behavior.
Handler stdout goes to stderr, a handler failure is logged rather than fatal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…din reads

Review findings: a posix-mode unset trap reported as - was eval'd; the
handler could stall shutdown, so it now gets the cancel hook's grace and
a /dev/null stdin; a signal recorded during any teardown pass re-raises;
the capture drops its temp-file round-trip for the plain substitution.
Suites share a server-script writer in tests/test_helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cross-call tool state travels through a server-owned exported file; the
section states the $$/BASHPID distinction and the detached-tool caveat.
The shutdown section carries the chained-handler rules and the corrected
cost of the subshell wrap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SpiGAndromeda
Martin Bens (SpiGAndromeda) merged commit 8833903 into main Sep 15, 2026
3 checks passed
Martin Bens (SpiGAndromeda) added a commit that referenced this pull request Sep 15, 2026
Classifies the chained EXIT handler from #26 as minor. The contract's major triggers are a renamed or resignatured function, a change to what a function writes to stdout, and a tightened validator; the chaining is none of them. A consumer's EXIT handler that `run_mcp_server` previously dropped now runs during teardown, so a consumer whose handler was silently discarded sees it execute after the upgrade. The entry also records the handler's stdin and subshell rules, which README.md and docs/architecture.md already state.

Co-Authored-By: Claude <noreply@anthropic.com>
@SpiGAndromeda
Martin Bens (SpiGAndromeda) deleted the feat/exit-trap-chaining branch September 15, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

run_mcp_server's traps replace the caller's, and a consumer has no way to keep its own cleanup

1 participant