/** * Detect the legacy `cleo-mcp-reaper` host artifacts (gh#1187). * * ## What this looks for, and why it is dangerous * * During a 2026-07-01 OOM investigation a helper script and systemd user timer * were applied by hand to reap leaked MCP server processes. They were never * package-owned and appear nowhere in CLEO's git history, so no upgrade path * has ever removed them — they simply keep running on every host that got them. * * The helper selects victims by `/proc/PID/comm === 'MainThread'` plus an age * threshold. That is a **generic Node launcher comm**, not an MCP signature. * The npm-distributed Codex CLI has a legitimate resident launcher with exactly * that comm, and it deliberately forwards SIGTERM to its native child — so the * reaper terminates a live Codex session cleanly, with no crash and no * coredump, which is why it was first misdiagnosed as a Codex bug. Journal * correlation on 2026-08-11 paired eight Codex exits with reaper kills to the * second. * * No process-title hardening upstream can make `pgrep -x MainThread` safe: the * discriminator is wrong in principle, because comm identifies a runtime, not * an owner. * * CLEO's own {@link ../gc/janitor.ts | janitor} already does this correctly — * scope/pgid as the primary discriminator, signature plus age only for * unregistered processes whose stdio peers are all dead, and scope reaping * restricted to `cleo-*` units inside `cleo.slice`. The canonical replacement * exists; the problem is purely that the unsafe predecessor is still installed. * * Detection only, unless `--fix`. The helper and its journal are evidence of a * real incident, so the repair **disables and masks** the units rather than * deleting anything. * * @module * @task T12131 */ /** * Whether a unit in this state can still fire. * * `masked` cannot start at all; `disabled` cannot be triggered by its own * timer. Anything else that exists is still capable of killing a live session. * * @param fileState - systemd `UnitFileState`. * @returns `true` when the unit remains dangerous. */ export declare function isUnitDangerous(fileState: string): boolean; /** State of one legacy systemd unit. */ export interface LegacyUnitState { /** Unit name, e.g. `cleo-mcp-reaper.timer`. */ readonly unit: string; /** `UnitFileState` — `enabled`, `disabled`, `masked`, or `absent`. */ readonly fileState: string; /** `ActiveState` — `active`, `inactive`, or `absent`. */ readonly activeState: string; /** `true` when this unit can still fire. */ readonly dangerous: boolean; } /** Outcome of a {@link scanLegacyReaper} run. */ export interface LegacyReaperReport { /** Legacy systemd units found on this host. */ readonly units: readonly LegacyUnitState[]; /** Absolute paths of legacy helper scripts still on disk. */ readonly scripts: readonly string[]; /** `true` when at least one unit can still fire and kill a live session. */ readonly armed: boolean; /** `true` when `--fix` ran and the units were disabled + masked. */ readonly remediated: boolean; /** Operator-facing next step. */ readonly recommendation: string; } /** * Scan the host for legacy `cleo-mcp-reaper` artifacts. * * @param opts - `fix: true` disables and masks any unit that can still fire. * @returns what was found, whether it is armed, and what to do next. * * @example * ```ts * const report = scanLegacyReaper(); * if (report.armed) console.error('a live Codex session can be killed at any time'); * ``` * * @task T12131 */ export declare function scanLegacyReaper(opts?: { fix?: boolean; }): LegacyReaperReport; /** * Compose the operator-facing next step for a scan result. * * Exported so the decision logic — which distinguishes "armed", "inert" and * "clean" — is testable without a systemd host. That distinction is the whole * value of the check: failing on inert leftovers would train operators to * ignore it, and passing on an armed timer would leave live sessions killable. * * @param r - the classified scan result. * @returns the operator-facing next step. */ export declare function buildRecommendation(r: { armed: boolean; remediated: boolean; units: readonly LegacyUnitState[]; scripts: readonly string[]; }): string; //# sourceMappingURL=legacy-reaper.d.ts.map