diff --git a/apps/server/src/routes/threads/actions.ts b/apps/server/src/routes/threads/actions.ts
index a59673fa2..b27eaee91 100644
--- a/apps/server/src/routes/threads/actions.ts
+++ b/apps/server/src/routes/threads/actions.ts
@@ -75,6 +75,7 @@ import {
 import {
   requireThreadCommandEnvironment,
   requireThreadHostCommandEnvironment,
+  resolveThreadHostCommandEnvironment,
 } from "../../services/threads/thread-command-environment.js";
 import {
   LIVE_DAEMON_COMMAND_TIMEOUT_MS,
@@ -652,7 +653,7 @@ export function registerThreadActionRoutes(app: Hono, deps: AppDeps): void {
       environmentId: thread.environmentId,
       excludeThreadId: thread.id,
     });
-    const environment = requireThreadHostCommandEnvironment({
+    const environment = resolveThreadHostCommandEnvironment({
       db: deps.db,
       thread,
     });
diff --git a/apps/server/src/services/threads/thread-archive.ts b/apps/server/src/services/threads/thread-archive.ts
index 66b1e2dc2..aea6d67df 100644
--- a/apps/server/src/services/threads/thread-archive.ts
+++ b/apps/server/src/services/threads/thread-archive.ts
@@ -20,13 +20,13 @@ import {
   requestActiveRuntimeThreadStopIfNeeded,
 } from "./thread-lifecycle.js";
 import { archiveThreadAndReleaseChildren } from "./thread-ownership.js";
-import { requireThreadHostCommandEnvironment } from "./thread-command-environment.js";
+import { resolveThreadHostCommandEnvironment } from "./thread-command-environment.js";
 
 interface ArchiveThreadWithLifecycleEffectsArgs {
   environment: {
     hostId: string;
     id: string;
-  };
+  } | null;
   thread: Pick<Thread, "environmentId" | "id" | "status">;
 }
 
@@ -53,12 +53,15 @@ export function archiveThreadWithLifecycleEffects(
     threadId: archivedThread.id,
   });
   // Archive only stops active runtime work; manual stop is the pre-start
-  // provisioning cancellation entrypoint.
-  requestActiveRuntimeThreadStopIfNeeded(
-    deps,
-    archivedThread,
-    args.environment,
-  );
+  // provisioning cancellation entrypoint. A thread whose environment row was
+  // pruned has no runtime left to stop.
+  if (args.environment !== null) {
+    requestActiveRuntimeThreadStopIfNeeded(
+      deps,
+      archivedThread,
+      args.environment,
+    );
+  }
   dispatchSettledArchivedThreadProviderArchiveCommand(deps, {
     threadId: archivedThread.id,
   });
@@ -90,7 +93,7 @@ export function archiveThreadAndHiddenSourceForks(
     sourceThreadId: archivedThread.id,
   })) {
     archiveThreadWithLifecycleEffects(deps, {
-      environment: requireThreadHostCommandEnvironment({
+      environment: resolveThreadHostCommandEnvironment({
         db: deps.db,
         thread: fork,
       }),
@@ -160,7 +163,7 @@ export function archiveThreadAndChildren(
   const affectedEnvironmentIds = new Set<string>();
 
   for (const thread of threads) {
-    const environment = requireThreadHostCommandEnvironment({
+    const environment = resolveThreadHostCommandEnvironment({
       db: deps.db,
       thread,
     });
@@ -172,7 +175,9 @@ export function archiveThreadAndChildren(
       continue;
     }
     archivedThreadIds.push(result.id);
-    affectedEnvironmentIds.add(environment.id);
+    if (environment !== null) {
+      affectedEnvironmentIds.add(environment.id);
+    }
   }
 
   for (const environmentId of affectedEnvironmentIds) {
diff --git a/apps/server/src/services/threads/thread-command-environment.ts b/apps/server/src/services/threads/thread-command-environment.ts
index e46e276b2..175aefed5 100644
--- a/apps/server/src/services/threads/thread-command-environment.ts
+++ b/apps/server/src/services/threads/thread-command-environment.ts
@@ -24,6 +24,25 @@ interface ThreadHostCommandEnvironment {
   id: string;
 }
 
+/**
+ * Resolve the host command environment for a thread, or null when the thread
+ * has no environment pointer. A thread loses its pointer when its environment
+ * row is pruned (threads.environment_id is ON DELETE SET NULL), so callers
+ * that only need the environment to stop live runtime work must tolerate null.
+ */
+export function resolveThreadHostCommandEnvironment(
+  args: RequireThreadHostCommandEnvironmentArgs,
+): ThreadHostCommandEnvironment | null {
+  if (args.thread.environmentId === null) {
+    return null;
+  }
+  const environment = requireEnvironment(args.db, args.thread.environmentId);
+  return {
+    id: environment.id,
+    hostId: environment.hostId,
+  };
+}
+
 export function requireThreadHostCommandEnvironment(
   args: RequireThreadHostCommandEnvironmentArgs,
 ): ThreadHostCommandEnvironment {
