mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
* fix(debugger): let REA exit when a debugger could not be stopped `rea debug-with-gdb` and `rea debug-with-rizin` printed cleanup_incomplete and set exit status 1, but the retained child and its pipes kept the event loop alive, so the process never exited. A stdio MCP server whose client disconnected hung the same way. closeOwnedDebuggerSessions is the owner both managers finish through. It now unreferences retained sessions, as supervised providers already do after incomplete cleanup, restores the references on retry, and names the retained process IDs. The one-shot commands report those IDs, since no later command can retry. Debugger ownership, signals and detach behavior are unchanged. GDB cleanup requests also no longer hold the host until their 120 s command deadline, because cleanup already bounds its own wait. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(debugger): name retained debuggers when an MCP server shuts down An MCP server that exits with a retained debugger logged only "REA provider cleanup failed" and "could not close cleanly", so nothing named the process left for the caller to check. The PIDs were on OwnedDebuggerCleanupError, but each shutdown reporter read only the outer aggregate's message, and the debug failure_cause description dropped the field too. The failure type now lives in its own dependency-free module with one reader that walks composed failures. The shutdown report, the server's onclose log, the debug description and the one-shot CLI all use it, and the shutdown report writes the same remediation the CLI returns. The fake debuggers now model each request exactly, with real reply shapes, and a request they do not model fails the test. Co-Authored-By: Claude Opus 5.5 <[email protected]> * test(debugger): compare retained-debugger guidance with its formatter The new checks froze the wording "Check process ... and its target". docs/testing.md asks guidance checks to compare with the owning formatter, alongside independent checks of the facts. They now do, and each still proves the process ID is named, because a formatter comparison alone would pass if the formatter dropped it. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
46 lines
1.8 KiB
TypeScript
46 lines
1.8 KiB
TypeScript
/** Closing all sessions could not confirm that these debuggers stopped. */
|
|
export class OwnedDebuggerCleanupError extends AggregateError {
|
|
/** Process IDs when cleanup gave up. Observations, not lasting proof of identity. */
|
|
readonly retainedProcessIds: readonly number[];
|
|
|
|
constructor(
|
|
failures: readonly Error[],
|
|
message: string,
|
|
retainedProcessIds: readonly number[],
|
|
) {
|
|
super(failures, message);
|
|
this.retainedProcessIds = retainedProcessIds;
|
|
}
|
|
}
|
|
|
|
/** Retained debugger process IDs anywhere in a composed cleanup failure. */
|
|
export const retainedDebuggerProcessIds = (
|
|
cause: unknown,
|
|
): readonly number[] => {
|
|
const found = new Set<number>();
|
|
const visited = new WeakSet<object>();
|
|
const visit = (node: unknown): void => {
|
|
if (typeof node !== "object" || node === null || visited.has(node)) return;
|
|
visited.add(node);
|
|
if (node instanceof OwnedDebuggerCleanupError)
|
|
for (const pid of node.retainedProcessIds) found.add(pid);
|
|
if (node instanceof AggregateError)
|
|
for (const error of node.errors) visit(error);
|
|
if (node instanceof Error) visit(node.cause);
|
|
};
|
|
visit(cause);
|
|
return [...found];
|
|
};
|
|
|
|
/** Log or diagnostic fields naming those processes; empty when none are known. */
|
|
export const retainedDebuggerDiagnostics = (
|
|
cause: unknown,
|
|
): { readonly retained_debugger_pids?: number[] } => {
|
|
const pids = retainedDebuggerProcessIds(cause);
|
|
return pids.length === 0 ? {} : { retained_debugger_pids: [...pids] };
|
|
};
|
|
|
|
/** What a caller must do once REA ends with these debuggers unconfirmed. */
|
|
export const retainedDebuggerRemediation = (pids: readonly number[]): string =>
|
|
`REA exits without confirming that the debugger stopped, and no later REA command can retry this cleanup. Check process ${pids.join(", ")} and its target, and stop them if they are still running.`;
|