Server Lifecycle
Feature Overview [edit]
Editors and IDEs do not always stop the spec-memo MCP server when they exit, leaving orphaned memo serve processes that hold the vault lock and stale in-memory buffers. memo shutdown (alias memo stop) is a local, network-free recovery command that discovers every running memo serve process on the machine, asks it to exit gracefully so its own handlers run (close(), fail-open flushOnShutdown with trigger: 'shutdown', telemetry drain, activity-bus close), waits, and only then force-terminates survivors.
Typical flow: memo shutdown --dry-run to preview, memo shutdown to stop gracefully, verify with memo status, then restart the IDE or memo serve and resume persistence with memo sync when batched vault-git is enabled. After restart, bootstrap pull and memo sync resume normal operation.
Business Rules & Logic [edit]
- Discovery match (
isMemoServeCommand): a process qualifies only when its command line contains a spec-memo server marker (spec-memoplusdist/cli.jsordist/mcp.js) and the standaloneservetoken.ng serve,yarn dev,yarn test, and othernodeprocesses never qualify. Canvas processes (memo canvas/serve-canvas, same marker + case-insensitivecanvas) classify ascanvas. - Scope: default stops stdio
memo serveplus SSEmemo serve --ssefor all vault roots.memo canvasis excluded unless--include-canvasis passed.--vaultRoot <path>narrows to instances whose command line contains that root, matched at a path-segment boundary after slash normalization (so.../.spec-memo-backupdoes not match scope.../.spec-memo). - Kill path: default
SIGTERMto each target PID, then poll for exit up to the effective timeout; exits inside the window reportstopped-graceful. Survivors are force-terminated (SIGKILL) and reportstopped-forced, orfailedif they survive force.--forceskips the graceful wait and force-terminates immediately.--dry-runlists matched PIDs with full command lines and takes no kill action. - Timeout resolution:
--timeout-ms <n>(must be a finite positive number) >SPEC_MEMO_SYNC_TIMEOUT_MS>8000ms. - Safety: the invoker never kills itself (own PID reported
skipped-self), never kills PID 0 or 1, and never kills a PID whose command line cannot be read or no longer matches at kill time (unreadable/mismatched entries areskipped). Before a lethal signal, the defaultverifyTargetre-lists processes and requires the same PID, command, and scope, guarding against PID reuse between discovery and kill. - Result vocabulary:
stopped-graceful,stopped-forced,already-exited,skipped-self,skipped,failed. PIDs that have already exited arealready-exited. - Exit codes:
0when nothing matched or every matched target ended stopped/already-exited/skipped; non-zero for invalid flags or at least one target stillfailed. A secondmemo shutdownwith no serving processes is idempotent:0with zero targets. - Bearer material is redacted from reported command lines:
--auth-token <value>is replaced with--auth-token [REDACTED]before stdout or--jsonoutput. - The invoker itself never writes vault records; each target's own shutdown handlers perform the git + hybrid flush fail-open.
memo statuskeeps its read-only posture and does not gain start/stop powers.
Technical Architecture [edit]
src/shutdown.ts:listMemoProcesses(Windows via PowerShellGet-CimInstance Win32_Process; POSIX viaps -ax -o pid=,args=),isMemoServeCommand,isCanvasCommand,classifyMemoCommand,matchesScopeRoot,filterShutdownTargets,redactCommandForDisplay,resolveShutdownTimeoutMs,defaultSignalOps(kill/isAlive/sleep),runShutdown.isAlivetreatsEPERMas alive.runShutdownreturns{ report, exitCode };reportis{ ok, dryRun, timeoutMs, targets, summary }with per-target{ pid, command, scope, result, reason? }and summary counts (stoppedGraceful,stoppedForced,alreadyExited,skipped,failed). Kill/poll logic is awaited; force confirmation waits up to 1000 ms.- CLI (
src/cli.ts):memo shutdown/memo stopaccepts--vaultRoot,--timeout-ms,--force,--dry-run,--include-canvas,--json; unknown flags exit non-zero with usage. Human output lists one line per PID with its result;--jsonemits the full report plus summary counts. Help lives under Utility Commands. - Graceful shutdown target handlers: SSE
memo serve --sseclose()(insrc/server.ts) runsflushOnShutdown, closes the activity bus and status companion, closes transports, and drains telemetry; stdiomemo serveclose()(insrc/mcp.ts) runsflushOnShutdownthenserver.close().src/dual-sync.tsflushOnShutdowncaps the wait and never throws. - No PID-file registry, no HTTP shutdown endpoint, no auto-restart. Remote-daemon shutdown is out of scope; shutdown is local-process only.
- Provenance:
0053-memo-shutdown.spec.md(issue #56). Related signal plumbing originated in0043-mcp-sse-transport.spec.mdand0033-vault-git-hybrid-sync.spec.md. See SSE Transport and Vault Sync.