# Files changed · PR \#22833 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/22833/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[fix\(server\): reap recorded Linux Claude survivors before recovery](https://git.cafe/pingdotgg/t3code/pull/22833.md)

Version: 19

Head commit: bc7446704898fbdd19e25542f0924e0472e9f655

Base commit: 96f09c6558b098f05ad0c027afa3846209c03d4f

Comparison base: 1688f965682d331da16a8c39f049592361f69c8d

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts

Status: modified; +55; −10

```diff
@@ -54,6 +54,7 @@
 import { parseCliArgs } from "@t3tools/shared/cliArgs";
 import { fromLenientJson } from "@t3tools/shared/schemaJson";
 import { AgentScope } from "@t3tools/shared/AgentScope";
+import * as ProviderProcesses from "../../process/providerProcesses.ts";
 import * as HostProcess from "@t3tools/shared/HostProcess";
 import { applyClaudePromptEffortPrefix } from "@t3tools/shared/model";
 import {
@@ -315,20 +316,61 @@
  * reads (#3089). Those stay in the CLI's group, so the rest of the group gets SIGKILL once the
  * CLI exits for any reason.
  */
+function spawnClaudeProcessGroup(
+  options: ClaudeSpawnOptions,
+  ownership: typeof ProviderProcesses.ProviderProcesses.Service,
+): ClaudeSpawnedProcess {
+  const register = ownership.register;
+  const child = NodeChildProcess.spawn(
+    register === undefined ? options.command : "/bin/sh",
+    register === undefined
+      ? options.args
+      : [
+          "-c",
+          'read -r ready <&3 || exit 1; exec 3<&-; exec "$@"',
+          "t3-provider",
+          options.command,
+          ...options.args,
+        ],
-function spawnClaudeProcessGroup(options: ClaudeSpawnOptions): ClaudeSpawnedProcess {
-  const child = NodeChildProcess.spawn(options.command, options.args, {
-    cwd: options.cwd,
-    env: options.env,
-    signal: options.signal,
-    stdio: ["pipe", "pipe", "ignore"],
+    {
+      cwd: options.cwd,
+      env: options.env,
+      signal: options.signal,
+      stdio:
+        register === undefined ? ["pipe", "pipe", "ignore"] : ["pipe", "pipe", "ignore", "pipe"],
+      detached: true,
+    },
+  );
+  let forget: (() => void) | undefined;
-    detached: true,
-  });
   child.once("exit", () => {
     try {
       signalProcessGroup(child.pid ?? 0, "SIGKILL");
     } catch {}
+    try {
+      forget?.();
+    } catch {}
   });
-  return child;
+  if (register !== undefined && child.pid !== undefined) {
+    const gate = child.stdio[3];
+    try {
+      forget = register(child.pid);
+      if (gate === null || gate === undefined || !("end" in gate)) {
+        throw new Error("Provider launch gate is unavailable");
+      }
+      gate.on("error", () => child.kill("SIGKILL"));
+      gate.end("ready\n");
+    } catch (cause) {
+      child.kill("SIGKILL");
+      gate?.destroy();
+      throw cause;
+    }
+  }
+
+  if (child.stdin === null || child.stdout === null) {
+    child.kill("SIGKILL");
+    throw new Error("Provider pipes are unavailable");
+  }
+  return Object.assign(child, { stdin: child.stdin, stdout: child.stdout });
 }
 
 export const ClaudeProviderCapabilitiesV2 = {
@@ -871,6 +913,7 @@
     const path = yield* Path.Path;
     const { native: nativeEventLogger } = yield* ProviderEventLoggers.ProviderEventLoggers;
     const agentScope = yield* AgentScope;
+    const providerProcesses = yield* ProviderProcesses.ProviderProcesses;
     const platform = yield* HostProcess.Platform;
 
     return ClaudeAgentSdkQueryRunner.of({
@@ -925,7 +968,9 @@
         // Windows has no process groups, so there the SDK spawns the CLI itself.
         const spawnClaudeCodeProcess =
           options.spawnClaudeCodeProcess ??
-          (platform === "win32" ? undefined : spawnClaudeProcessGroup);
+          (platform === "win32"
+            ? undefined
+            : (options: ClaudeSpawnOptions) => spawnClaudeProcessGroup(options, providerProcesses));
         const queryRuntime = yield* Effect.uninterruptibleMask((restore) =>
           Effect.try({
             try: () =>
```

## apps/server/src/process/agentScope.test.ts

Status: modified; +350; −21

```diff
@@ -1,20 +1,30 @@
 // @effect-diagnostics nodeBuiltinImport:off -- fake systemd binaries live in a temp dir.
+import * as NodeChildProcess from "node:child_process";
+import * as NodeCrypto from "node:crypto";
 import * as NodeFS from "node:fs";
 import * as NodeOS from "node:os";
 import * as NodePath from "node:path";
 
 import * as NodeServices from "@effect/platform-node/NodeServices";
 import { assert, describe, expect, it } from "@effect/vitest";
+import { ProviderSessionId, ThreadId } from "@t3tools/contracts";
+import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
+import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
 import { AgentScope } from "@t3tools/shared/AgentScope";
 import * as HostProcess from "@t3tools/shared/HostProcess";
+import * as Deferred from "effect/Deferred";
 import * as Effect from "effect/Effect";
 import * as Layer from "effect/Layer";
+import type * as Scope from "effect/Scope";
+import * as Schema from "effect/Schema";
 import { ChildProcess, ChildProcessSpawner } from "effect/process";
 
+import * as ClaudeAdapterV2 from "../orchestration-v2/Adapters/ClaudeAdapterV2.ts";
 import * as ServerConfig from "../config.ts";
 import { makeCodexAppServerSpawnCommand } from "../orchestration-v2/Adapters/CodexAdapterV2.ts";
 import * as ProcessRunner from "../processRunner.ts";
+import * as ProviderProcesses from "./providerProcesses.ts";
+import { classifyScope, layer, make, ownedAgentSlice, type ScopeState } from "./agentScope.ts";
-import { classifyScope, make, ownedAgentSlice, type ScopeState } from "./agentScope.ts";
 
 const running: ScopeState = {
   loadState: "loaded",
@@ -42,7 +52,15 @@
     readonly calls: ReadonlyArray<ReadonlyArray<string>>;
     readonly stateDir: string;
   }) => Effect.Effect<A, E>,
+  {
+    oomPolicy = true,
-  { oomPolicy = true }: { readonly oomPolicy?: boolean } = {},
+    leftovers = false,
+    liveOwner = false,
+  }: {
+    readonly oomPolicy?: boolean;
+    readonly leftovers?: boolean;
+    readonly liveOwner?: boolean;
+  } = {},
 ) =>
   Effect.gen(function* () {
     const bin = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-agent-scope-"));
@@ -79,12 +97,15 @@
           );
         }),
     });
+    const { stateDir } = yield* ServerConfig.ServerConfig;
+    if (leftovers)
+      units.set(ownedAgentSlice(stateDir), { activeState: "active", result: "success" });
     const scope = yield* make.pipe(
+      Effect.provideService(ProviderProcesses.ProviderProcesses, { hasOtherLiveOwner: liveOwner }),
       Effect.provideService(ProcessRunner.ProcessRunner, runner),
       Effect.provideService(HostProcess.Platform, "linux"),
       Effect.provideService(HostProcess.Environment, { PATH: bin, XDG_RUNTIME_DIR: "/run/user/1" }),
     );
-    const { stateDir } = yield* ServerConfig.ServerConfig;
     return yield* body({ scope, units, calls, stateDir }).pipe(
       Effect.ensuring(Effect.sync(() => NodeFS.rmSync(bin, { recursive: true, force: true }))),
     );
@@ -174,24 +195,332 @@
     ),
   );
 
+  it.effect("stops agents a killed server left during acquisition before the first launch", () =>
+    withFakeSystemd(
+      ({ scope, calls, stateDir }) =>
+        Effect.gen(function* () {
+          const stops = calls.filter(([, verb]) => verb === "stop");
+          expect(stops).toEqual([["--user", "stop", ownedAgentSlice(stateDir)]]);
+          const agent = yield* scope.wrap({ command: "agent", args: [], name: "t", threadId: "a" });
+          expect(sliceOf(agent)).toBe(ownedAgentSlice(stateDir));
+          expect(agent.args).toContain("--property=TimeoutStopSec=10s");
+          const terminal = yield* scope.wrap({
+            command: "agent",
+            args: [],
+            name: "terminal",
+            survivesRestart: true,
+          });
+          expect(sliceOf(terminal)).toBe("app-t3code-agents.slice");
+          expect(calls.filter(([, verb]) => verb === "stop")).toHaveLength(1);
+        }),
+      { leftovers: true },
+    ),
+  );
+
+  it.effect("does not stop the owned slice while another recorded server is alive", () =>
+    withFakeSystemd(
+      ({ calls }) =>
+        Effect.sync(() => {
+          expect(calls.filter(([, verb]) => verb === "stop")).toEqual([]);
+        }),
+      { leftovers: true, liveOwner: true },
+    ),
+  );
+});
+
+// These fixtures only signal PIDs captured at spawn; no server or provider credentials are used.
+const ownedProcessFixture = Effect.gen(function* () {
+  const ready = yield* Deferred.make<void>();
+  const exited = yield* Deferred.make<void>();
+  const child = yield* Effect.acquireRelease(
+    Effect.sync(() => {
+      const child = NodeChildProcess.spawn(
+        process.execPath,
+        ["-e", 'process.stdin.resume(); process.send("ready");'],
+        { stdio: ["pipe", "ignore", "ignore", "ipc"] },
+      );
+      child.once("message", () => Deferred.doneUnsafe(ready, Effect.void));
+      child.once("error", (error) => Deferred.doneUnsafe(ready, Effect.die(error)));
+      child.once("exit", () => Deferred.doneUnsafe(exited, Effect.void));
+      return child;
+    }),
+    (child) =>
+      Effect.sync(() => child.kill("SIGKILL")).pipe(Effect.andThen(Deferred.await(exited))),
+  );
+  yield* Deferred.await(ready);
+  assert(child.pid !== undefined);
+  const controller = AcpSessionRuntime.makePosixProcessTreeController("linux");
+  const pid = child.pid;
+  const identity = controller.identity(pid);
+  assert(identity !== undefined);
+  const boot = NodeFS.readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
+  return {
+    pid,
+    startTime: `${boot}:${identity.startTime}`,
+    pgid: 0,
+    running: () => {
+      const observed = controller.identity(pid);
+      return observed !== undefined && !AcpSessionRuntime.posixProcessIsZombie(observed);
+    },
+    stop: Effect.sync(() => child.kill("SIGKILL")).pipe(Effect.andThen(Deferred.await(exited))),
+  };
+});
+
+const withRestartOwnership = <A, E, R>(
+  body: (input: {
+    readonly record: (
+      owner: { readonly pid: number; readonly startTime: string },
+      processes: ReadonlyArray<{
+        readonly pid: number;
+        readonly startTime: string;
+        readonly pgid: number;
+      }>,
+    ) => void;
+    readonly restart: Effect.Effect<void, never, Scope.Scope>;
+  }) => Effect.Effect<A, E, R>,
+) =>
+  Effect.gen(function* () {
+    const config = yield* ServerConfig.ServerConfig;
+    const { stateDir } = config;
+    const directory = NodePath.join(stateDir, "provider-processes");
+    NodeFS.mkdirSync(directory, { recursive: true });
+    const record = (
+      owner: { readonly pid: number; readonly startTime: string },
+      processes: ReadonlyArray<{
+        readonly pid: number;
+        readonly startTime: string;
+        readonly pgid: number;
+      }>,
+    ) => {
+      const instanceId = NodeCrypto.randomUUID();
+      NodeFS.writeFileSync(
+        NodePath.join(directory, `${instanceId}.json`),
+        JSON.stringify({
+          version: 1,
+          instanceId,
+          hostname: NodeOS.hostname(),
+          stateDir,
+          owner,
+          processes,
+        }),
+      );
+    };
+    // Acquiring the production layer is the barrier before orchestration recovery may run.
+    const restart = Layer.build(layer).pipe(
+      Effect.provideService(ServerConfig.ServerConfig, config),
+      Effect.provide(NodeServices.layer),
+      Effect.asVoid,
+    );
+    return yield* body({ record, restart });
+  }).pipe(
+    Effect.scoped,
+    Effect.provideService(HostProcess.Platform, "linux"),
+    Effect.provideService(HostProcess.Environment, { PATH: process.env.PATH }),
+    Effect.provide(
+      ServerConfig.layerTest(process.cwd(), { prefix: "t3-restart-ownership-" }).pipe(
+        Layer.provideMerge(NodeServices.layer),
+      ),
+    ),
+  );
+
+describe.runIf(HostProcess.Platform.defaultValue() === "linux")(
+  "provider restart ownership without systemd",
+  () => {
+    it.live(
+      "stops both recorded survivors before startup completes and leaves an unrecorded process alone",
+      () =>
+        withRestartOwnership(({ record, restart }) =>
+          Effect.gen(function* () {
+            const owner = yield* ownedProcessFixture;
+            const first = yield* ownedProcessFixture;
+            const second = yield* ownedProcessFixture;
+            const unrelated = yield* ownedProcessFixture;
+            record(owner, [first]);
+            record(owner, [second]);
+            yield* owner.stop;
+
+            yield* restart;
+
+            expect([first.running(), second.running()]).toEqual([false, false]);
+            expect(unrelated.running()).toBe(true);
+          }),
+        ),
+    );
+
+    it.live(
+      "preserves a live server's provider and a reused provider PID while reaping a matching orphan",
+      () =>
+        withRestartOwnership(({ record, restart }) =>
+          Effect.gen(function* () {
+            const deadOwner = yield* ownedProcessFixture;
+            const liveOwner = yield* ownedProcessFixture;
+            const liveProvider = yield* ownedProcessFixture;
+            const reusedProvider = yield* ownedProcessFixture;
+            const orphan = yield* ownedProcessFixture;
+            record(liveOwner, [liveProvider]);
+            record(deadOwner, [
+              { ...reusedProvider, startTime: reusedProvider.startTime.replace(/:\d+$/, ":1") },
+            ]);
+            record(deadOwner, [orphan]);
+            yield* deadOwner.stop;
+
+            yield* restart;
+
+            expect(liveProvider.running()).toBe(true);
+            expect(reusedProvider.running()).toBe(true);
+            expect(orphan.running()).toBe(false);
+          }),
+        ),
+    );
+
+    it.live("does not mistake a reused owner PID for a live owning server", () =>
+      withRestartOwnership(({ record, restart }) =>
+        Effect.gen(function* () {
+          const reusedOwner = yield* ownedProcessFixture;
+          const orphan = yield* ownedProcessFixture;
+          record({ ...reusedOwner, startTime: reusedOwner.startTime.replace(/:\d+$/, ":1") }, [
+            orphan,
+          ]);
+
+          yield* restart;
+
+          expect(reusedOwner.running()).toBe(true);
+          expect(orphan.running()).toBe(false);
+        }),
+      ),
+    );
+  },
+);
+
+const LaunchObservation = Schema.Struct({
+  pid: Schema.Number,
+  record: Schema.Struct({
+    version: Schema.Literal(1),
+    owner: Schema.Struct({ pid: Schema.Number, startTime: Schema.String }),
+    processes: Schema.Array(Schema.Struct({ pid: Schema.Number, startTime: Schema.String })),
+  }),
+});
+
+// The fake CLI reports ownership at entry and answers SDK control requests.
+// It never starts a provider or reads provider credentials.
+const decodeLaunchObservation = Schema.decodeEffect(Schema.fromJsonString(LaunchObservation));
+
+const FAKE_CLI = `
+import * as fs from "node:fs";
+import * as path from "node:path";
+import * as readline from "node:readline";
+const directory = process.env.T3_TEST_PROCESS_DIRECTORY;
+const ready = process.env.T3_TEST_PROCESS_READY;
+const records = fs.readdirSync(directory)
+  .filter((file) => file.endsWith(".json"))
+  .map((file) => JSON.parse(fs.readFileSync(path.join(directory, file), "utf8")));
+const record = records.find((record) => record.processes.some((entry) => entry.pid === process.pid));
+fs.writeFileSync(ready + ".pending", JSON.stringify({ pid: process.pid, record }));
+fs.renameSync(ready + ".pending", ready);
+readline.createInterface({ input: process.stdin }).on("line", (line) => {
+  const frame = JSON.parse(line);
+  if (frame.type !== "control_request") return;
+  process.stdout.write(JSON.stringify({
+    type: "control_response",
+    response: { subtype: "success", request_id: frame.request_id, response: {} },
+  }) + "\\n");
+});
+`;
+
+describe.runIf(HostProcess.Platform.defaultValue() === "linux")("Claude process ownership", () => {
+  it.live("records the production SDK child before it executes and forgets it after exit", () =>
+    Effect.gen(function* () {
+      const config = yield* ServerConfig.ServerConfig;
+      const directory = NodePath.join(config.stateDir, "provider-processes");
+      const readyPath = NodePath.join(config.stateDir, "ready.json");
+      const cliPath = NodePath.join(config.stateDir, "fake-cli.mjs");
+      NodeFS.writeFileSync(cliPath, FAKE_CLI);
+      const ready = yield* Deferred.make<void>();
+      const removed = yield* Deferred.make<void>();
+      yield* Effect.acquireRelease(
+        Effect.sync(() =>
+          NodeFS.watch(config.stateDir, (_, file) => {
+            if (file === "ready.json" && NodeFS.existsSync(readyPath)) {
+              Deferred.doneUnsafe(ready, Effect.void);
+            }
+          }),
+        ),
+        (watcher) => Effect.sync(() => watcher.close()),
+      );
-  it.effect("stops agents a killed server left before the first launch", () =>
-    withFakeSystemd(({ scope, units, calls, stateDir }) =>
-      Effect.gen(function* () {
-        units.set(ownedAgentSlice(stateDir), { activeState: "active", result: "success" });
-        const agent = yield* scope.wrap({ command: "agent", args: [], name: "t", threadId: "a" });
-        const stops = calls.filter(([, verb]) => verb === "stop");
+      yield* Effect.gen(function* () {
+        const runner = yield* ClaudeAdapterV2.ClaudeAgentSdkQueryRunner;
-        expect(stops).toEqual([["--user", "stop", ownedAgentSlice(stateDir)]]);
+        yield* Effect.acquireRelease(
+          Effect.sync(() =>
+            NodeFS.watch(directory, (_, file) => {
-        expect(sliceOf(agent)).toBe(ownedAgentSlice(stateDir));
-        expect(agent.args).toContain("--property=TimeoutStopSec=10s");
-        const terminal = yield* scope.wrap({
-          command: "agent",
-          args: [],
-          name: "terminal",
-          survivesRestart: true,
-        });
-        expect(sliceOf(terminal)).toBe("app-t3code-agents.slice");
-        expect(calls.filter(([, verb]) => verb === "stop")).toHaveLength(1);
-      }),
+              if (
+                file !== null &&
+                file.endsWith(".json") &&
+                !NodeFS.existsSync(NodePath.join(directory, file))
+              ) {
+                Deferred.doneUnsafe(removed, Effect.void);
+              }
+            }),
+          ),
+          (watcher) => Effect.sync(() => watcher.close()),
+        );
+        yield* Effect.gen(function* () {
+          yield* Effect.acquireRelease(
+            runner.open({
+              threadId: ThreadId.make("ownership-thread"),
+              providerSessionId: ProviderSessionId.make("ownership-session"),
+              options: {
+                ...ClaudeAdapterV2.makeClaudeQueryOptions({
+                  modelSelection: {
+                    instanceId: ClaudeAdapterV2.CLAUDE_DEFAULT_INSTANCE_ID,
+                    model: "claude-sonnet-4-6",
+                  },
+                  nativeThreadId: NodeCrypto.randomUUID(),
+                  resume: false,
+                  cwd: config.stateDir,
+                }),
+                pathToClaudeCodeExecutable: cliPath,
+                env: {
+                  PATH: `${NodePath.dirname(process.execPath)}:/usr/bin:/bin`,
+                  T3_TEST_PROCESS_DIRECTORY: directory,
+                  T3_TEST_PROCESS_READY: readyPath,
+                },
+              },
+            }),
+            (session) => session.close.pipe(Effect.orDie),
+          );
+          yield* Deferred.await(ready);
+          const observation = yield* decodeLaunchObservation(
+            NodeFS.readFileSync(readyPath, "utf8"),
+          );
+          assert.equal(observation.record.owner.pid, process.pid);
+          assert.deepEqual(
+            observation.record.processes.map(({ pid }) => pid),
+            [observation.pid],
+          );
+          const boot = NodeFS.readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
+          assert.isTrue(observation.record.processes[0]?.startTime.startsWith(`${boot}:`));
+          const files = NodeFS.readdirSync(directory).filter((file) => file.endsWith(".json"));
+          // One record belongs to the server; the other belongs to this CLI.
+          assert.lengthOf(files, 2);
+        }).pipe(Effect.scoped);
+        yield* Deferred.await(removed);
+      }).pipe(
+        Effect.provide(ClaudeAdapterV2.layerQueryRunner),
+        Effect.provideService(
+          ProviderEventLoggers.ProviderEventLoggers,
+          ProviderEventLoggers.NoOpProviderEventLoggers,
+        ),
+        Effect.provide(layer),
+      );
+    }).pipe(
+      Effect.scoped,
+      Effect.provideService(HostProcess.Platform, "linux"),
+      Effect.provideService(HostProcess.Environment, { PATH: process.env.PATH }),
+      Effect.provide(
+        ServerConfig.layerTest(process.cwd(), { prefix: "t3-process-launch-" }).pipe(
+          Layer.provideMerge(NodeServices.layer),
+        ),
+      ),
     ),
   );
 });
```

## apps/server/src/process/agentScope.ts

Status: modified; +11; −1

```diff
@@ -17,6 +17,7 @@
 
 import * as ServerConfig from "../config.ts";
 import * as ProcessRunner from "../processRunner.ts";
+import * as ProviderProcesses from "./providerProcesses.ts";
 
 const AGENT_SLICE = "app-t3code-agents.slice";
 
@@ -100,6 +101,7 @@
   const path = yield* Path.Path;
   const hostEnvironment = yield* HostProcess.Environment;
   const { stateDir } = yield* ServerConfig.ServerConfig;
+  const { hasOtherLiveOwner } = yield* ProviderProcesses.ProviderProcesses;
   const ownedSlice = ownedAgentSlice(stateDir);
   const layerScope = yield* Effect.scope;
 
@@ -124,6 +126,7 @@
   const stopLeftoverAgents = Effect.fn("agentScope.stopLeftoverAgents")(function* (
     systemctl: string,
   ) {
+    if (hasOtherLiveOwner === true) return;
     const shown = yield* run(systemctl, [
       "--user",
       "show",
@@ -201,6 +204,10 @@
     }),
   );
 
+  // Layer acquisition is the startup barrier: recovery must not terminalize or
+  // continue old runs while a previous server's providers can still execute.
+  yield* systemd;
+
   const scopeByThread = new Map<string, { unit: string; oomKilled: boolean }>();
 
   const readScope = Effect.fn("agentScope.readScope")(function* (systemctl: string, unit: string) {
@@ -348,4 +355,7 @@
   } satisfies AgentScopeShape;
 });
 
+export const layer = Layer.effect(AgentScope, make).pipe(
-export const layer = Layer.effect(AgentScope, make).pipe(Layer.provide(ProcessRunner.layer));
+  Layer.provide(ProcessRunner.layer),
+  Layer.provideMerge(ProviderProcesses.layer),
+);
```

## apps/server/src/process/providerProcesses.ts

Status: added; +131; −0

```diff
@@ -1,0 +1,131 @@
+// @effect-diagnostics nodeBuiltinImport:off -- the SDK's synchronous spawn callback must record ownership before releasing the child.
+import * as NodeCrypto from "node:crypto";
+import * as NodeFS from "node:fs";
+import * as NodeOS from "node:os";
+import * as NodePath from "node:path";
+
+import * as AcpSessionRuntime from "@t3tools/provider-acp/server/AcpSessionRuntime";
+import * as HostProcess from "@t3tools/shared/HostProcess";
+import * as Context from "effect/Context";
+import * as Effect from "effect/Effect";
+import * as Layer from "effect/Layer";
+import * as Schema from "effect/Schema";
+
+import * as ServerConfig from "../config.ts";
+
+const ProcessIdentity = Schema.Struct({
+  pid: Schema.Int.check(Schema.isBetween({ minimum: 2, maximum: Number.MAX_SAFE_INTEGER })),
+  startTime: Schema.String,
+});
+const OwnershipRecord = Schema.Struct({
+  version: Schema.Literal(1),
+  instanceId: Schema.String,
+  hostname: Schema.String,
+  stateDir: Schema.String,
+  owner: ProcessIdentity,
+  processes: Schema.Array(Schema.Struct({ ...ProcessIdentity.fields, pgid: Schema.Int })),
+});
+const decodeRecord = Schema.decodeUnknownSync(Schema.fromJsonString(OwnershipRecord));
+
+export const ProviderProcesses = Context.Reference<{
+  readonly register?: (pid: number) => () => void;
+  readonly hasOtherLiveOwner?: boolean;
+}>("t3/process/ProviderProcesses", { defaultValue: () => ({}) });
+
+const make = Effect.gen(function* () {
+  const platform = yield* HostProcess.Platform;
+  if (platform !== "linux") return {};
+  const { stateDir } = yield* ServerConfig.ServerConfig;
+  const directory = NodePath.join(stateDir, "provider-processes");
+  const hostname = NodeOS.hostname();
+  const boot = NodeFS.readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
+  const controller = AcpSessionRuntime.makePosixProcessTreeController(platform);
+  const identity = (pid: number) => {
+    const observed = controller.identity(pid);
+    if (observed === undefined || AcpSessionRuntime.posixProcessIsZombie(observed))
+      return undefined;
+    return { ...observed, startTime: `${boot}:${observed.startTime}` };
+  };
+  const matches = (expected: typeof ProcessIdentity.Type) =>
+    identity(expected.pid)?.startTime === expected.startTime;
+  const owner = identity(process.pid);
+  if (owner === undefined) return yield* Effect.die(new Error("Cannot identify provider owner"));
+  NodeFS.mkdirSync(directory, { recursive: true });
+
+  const writeRecord = (processes: (typeof OwnershipRecord.Type)["processes"]) => {
+    const instanceId = NodeCrypto.randomUUID();
+    const recordPath = NodePath.join(directory, `${instanceId}.json`);
+    const pendingPath = `${recordPath}.pending`;
+    NodeFS.writeFileSync(
+      pendingPath,
+      JSON.stringify({
+        version: 1,
+        instanceId,
+        hostname,
+        stateDir,
+        owner: { pid: owner.pid, startTime: owner.startTime },
+        processes,
+      } satisfies typeof OwnershipRecord.Type),
+    );
+    NodeFS.renameSync(pendingPath, recordPath);
+    return () => NodeFS.rmSync(recordPath, { force: true });
+  };
+  // Publish the server identity even before its first provider. Another server
+  // using this home must not stop the shared systemd slice while we are alive.
+  const forgetOwner = writeRecord([]);
+  yield* Effect.addFinalizer(() => Effect.sync(forgetOwner));
+  let hasOtherLiveOwner = false;
+
+  // Only immutable per-launch records are shared between server instances. A
+  // live owner's records belong to that server, even when it uses this home.
+  for (const file of NodeFS.readdirSync(directory)) {
+    if (!file.endsWith(".json")) continue;
+    const recordPath = NodePath.join(directory, file);
+    let record: typeof OwnershipRecord.Type;
+    try {
+      record = decodeRecord(NodeFS.readFileSync(recordPath, "utf8"));
+    } catch {
+      // A concurrent exit can remove the record. Incomplete/unknown records
+      // never authorize a signal.
+      continue;
+    }
+    if (record.hostname !== hostname || record.stateDir !== stateDir) continue;
+    if (matches(record.owner)) {
+      if (record.owner.pid !== process.pid) hasOtherLiveOwner = true;
+      continue;
+    }
+    for (const survivor of record.processes) {
+      if (survivor.pid === process.pid || !matches(survivor)) continue;
+      // Never infer ownership from argv, executable names, or group membership.
+      // As in the ACP controller, starttime blocks reuse between observations.
+      // Node has no pidfd_send_signal, so the final read-to-signal race remains.
+      try {
+        controller.signalProcess(survivor.pid, "SIGKILL");
+      } catch (cause) {
+        if (matches(survivor)) return yield* Effect.die(cause);
+      }
+      for (let attempt = 0; matches(survivor); attempt++) {
+        if (attempt === 100) {
+          return yield* Effect.die(new Error("Recorded provider survived restart cleanup"));
+        }
+        yield* Effect.sleep("10 millis");
+      }
+    }
+    NodeFS.rmSync(recordPath, { force: true });
+  }
+
+  return {
+    hasOtherLiveOwner,
+    register: (pid: number) => {
+      const provider = identity(pid);
+      if (provider === undefined || provider.ppid !== process.pid) {
+        throw new Error("Cannot identify spawned provider");
+      }
+      // A rename keeps startup from seeing a partial write. The child is gated
+      // until this returns, so an abrupt death cannot leave an unrecorded CLI.
+      return writeRecord([{ pid, startTime: provider.startTime, pgid: provider.pgid }]);
+    },
+  };
+});
+
+export const layer = Layer.effect(ProviderProcesses, make);
```
