agent-file-tools 0.55.1

Agent File Tools — tree-sitter powered code analysis for AI agents
Documentation
1
{"data":{"repository":{"nameWithOwner":"cortexkit/aft","pullRequest":{"number":270,"reviews":{"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 10 files\n<!-- cubic:review-summary:end -->\n\n<sub>Reply with feedback, questions, or to request a fix.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787843986274_774ba190-1e95-4b7f-9eb1-cfe68a5fe71d?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787843986274_774ba190-1e95-4b7f-9eb1-cfe68a5fe71d:5b698395a950b43fb8dc38f66b5aef4675762446:b6e934a6-9137-4dff-adc7-476b174701be -->","state":"COMMENTED","submittedAt":"2026-08-27T15:26:16Z","comments":{"totalCount":2,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=b39c3dea-998e-4fc6-93bd-79ac86741cce -->\n<!-- cubic:review-run=b6e934a6-9137-4dff-adc7-476b174701be -->\n<!-- metadata:{\"confidence\":9} -->\nP1: When the Windows install times out or is aborted, `proc.kill()` terminates only the new `cmd.exe` wrapper, not the `node.exe` process launched by `npm.cmd`. The install can therefore continue after `runNpmInstallSafe` restores the snapshot, corrupting the rollback and leaking a background npm process. Terminate the Windows process tree before restoring the snapshot.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/hooks/auto-update-checker/cache.ts, line 274:\n\n<comment>When the Windows install times out or is aborted, `proc.kill()` terminates only the new `cmd.exe` wrapper, not the `node.exe` process launched by `npm.cmd`. The install can therefore continue after `runNpmInstallSafe` restores the snapshot, corrupting the rollback and leaking a background npm process. Terminate the Windows process tree before restoring the snapshot.</comment>\n\n<file context>\n@@ -264,15 +264,19 @@ export async function runNpmInstallSafe(\n+      \"--no-progress\",\n+      \"--ignore-scripts\",\n+    ]);\n+    const proc = spawn(invocation.command, invocation.args, {\n+      cwd: installDir,\n+      stdio: [\"ignore\", \"pipe\", \"pipe\"],\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`7848e45`](https://github.com/cortexkit/aft/commit/7848e45daa785fa1f2519fdefe89431e615ef241)","createdAt":"2026-08-27T15:26:16Z","updatedAt":"2026-08-27T16:00:25Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=be3b3dc3-89bf-403b-aef4-762d8a105189 -->\n<!-- cubic:review-run=b6e934a6-9137-4dff-adc7-476b174701be -->\n<!-- metadata:{\"confidence\":8} -->\nP3: When npm is installed under a Windows path containing a literal `%`, `npmInvocation()` rejects the valid shim path before it can run. Escape percent characters using a cmd-compatible strategy, or provide a non-shell invocation fallback instead of rejecting that path.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/aft-bridge/src/npm-resolver.ts, line 226:\n\n<comment>When npm is installed under a Windows path containing a literal `%`, `npmInvocation()` rejects the valid shim path before it can run. Escape percent characters using a cmd-compatible strategy, or provide a non-shell invocation fallback instead of rejecting that path.</comment>\n\n<file context>\n@@ -185,6 +193,51 @@ export function resolveNpm(deps: ResolveNpmDeps = defaultDeps()): ResolvedNpm |\n+    return { command: resolved.command, args: [...npmArgs] };\n+  }\n+\n+  if (/[\\0\\r\\n\"%]/.test(resolved.command)) {\n+    throw new Error(\n+      `npm command cannot be represented safely for cmd.exe: ${JSON.stringify(resolved.command)}`,\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`7848e45`](https://github.com/cortexkit/aft/commit/7848e45daa785fa1f2519fdefe89431e615ef241)","createdAt":"2026-08-27T15:26:16Z","updatedAt":"2026-08-27T16:00:24Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T15:37:32Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Cancellation leaves npm running**\n\nWhen a Windows `npm.cmd` invocation is aborted or times out, callers terminate the new `cmd.exe` child and proceed as though npm has stopped, while its npm/node descendant can continue writing. This races snapshot restoration or subsequent installation in the same cache directory, and doctor can report failure while the update continues in the background.\n\n**Knowledge Base Used:** [Bridge storage and installation](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/bridge-storage-and-installation.md)","createdAt":"2026-08-27T15:37:31Z","updatedAt":"2026-08-27T15:37:32Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:00:07Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 7848e45d. Added shared Windows process-tree termination via taskkill.exe /PID /T /F and integrated it into auto-update rollback, both LSP shutdown paths, and doctor timeout handling. The rollback and LSP completion paths await termination. A Windows regression test verifies a cmd.exe -> node.exe descendant cannot perform a delayed sentinel write after cancellation.","createdAt":"2026-08-27T16:00:07Z","updatedAt":"2026-08-27T16:00:08Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:00:08Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 7848e45d. The resolved shim path is now passed through an AFT_NPM_COMMAND environment variable instead of interpolated into cmd.exe command text. cmd expansion is single-pass, so literal percent sequences in the path remain literal. Added a Windows execution test that successfully probes npm.cmd from a directory containing a literal %TEMP% segment.","createdAt":"2026-08-27T16:00:07Z","updatedAt":"2026-08-27T16:00:08Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:00:08Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 7848e45d. Windows cmd-shim cancellation now uses a shared terminateNpmProcessTree() helper backed by taskkill.exe /PID /T /F. runNpmInstallSafe awaits tree termination before restoring its snapshot; both LSP installers wait before completing/releasing their lock; and doctor now uses async spawn so its timeout can terminate the tree before reporting failure. Added a Windows regression test with a cmd.exe -> node.exe descendant and a delayed sentinel write.","createdAt":"2026-08-27T16:00:07Z","updatedAt":"2026-08-27T16:00:08Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 10 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>Reply with feedback, questions, or to request a fix.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787846398884_8c9a1192-e0bd-441a-a18f-bc3ccd00a54b?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787846398884_8c9a1192-e0bd-441a-a18f-bc3ccd00a54b:7848e45daa785fa1f2519fdefe89431e615ef241:f3dc5e06-9e51-44f9-ba4b-5b77906112be -->","state":"COMMENTED","submittedAt":"2026-08-27T16:15:07Z","comments":{"totalCount":3,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=f3b7c560-8761-4f2e-ad84-30443f0b13b4 -->\n<!-- cubic:review-run=f3dc5e06-9e51-44f9-ba4b-5b77906112be -->\n<!-- metadata:{\"confidence\":9} -->\nP2: When `npm install` writes enough normal output, this async child blocks on the unread stdout pipe, reaches the 120-second timeout, and reports a failed update. Discard stdout or consume it.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/aft-cli/src/commands/doctor.ts, line 681:\n\n<comment>When `npm install` writes enough normal output, this async child blocks on the unread stdout pipe, reaches the 120-second timeout, and reports a failed update. Discard stdout or consume it.</comment>\n\n<file context>\n@@ -660,6 +666,56 @@ function findSchemaFixTargets(adapters: HarnessAdapter[]): SchemaFixTarget[] {\n+    const child = spawn(invocation.command, invocation.args, {\n+      cwd: installDir,\n+      env: { ...npmSpawnEnv(npm), ...invocation.env },\n+      stdio: [\"ignore\", \"pipe\", \"pipe\"],\n+      windowsVerbatimArguments: invocation.windowsVerbatimArguments,\n+    });\n</file context>\n```\n\n</details>\n\n```suggestion\n      stdio: [\"ignore\", \"ignore\", \"pipe\"],\n```\n\n✅ Addressed in [`a696a22`](https://github.com/cortexkit/aft/commit/a696a22b73084ddc6f67b81ba89e74607d8bc922)","createdAt":"2026-08-27T16:15:07Z","updatedAt":"2026-08-27T16:43:42Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=5047bdd8-6039-4b46-b737-7f0bb06eb89e -->\n<!-- cubic:review-run=f3dc5e06-9e51-44f9-ba4b-5b77906112be -->\n<!-- metadata:{\"confidence\":6} -->\nP2: The non-Windows branch calls `child.kill()` then immediately resolves, so `await terminateNpmProcessTree(...)` does not actually wait for the process to exit. The Windows cmd-shim path awaits `taskkill`'s exit before resolving, so all current callers (cleanup-then-restore rollback, LSP shutdown, doctor timeout) rely on the awaited promise meaning \"the npm process stopped\" — but on Unix the promise resolves while npm may still be running and writing, so the subsequent directory removal/state restore can race. Make the non-shim path also await the child's `exit` event to match the Windows guarantee.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/aft-bridge/src/npm-resolver.ts, line 267:\n\n<comment>The non-Windows branch calls `child.kill()` then immediately resolves, so `await terminateNpmProcessTree(...)` does not actually wait for the process to exit. The Windows cmd-shim path awaits `taskkill`'s exit before resolving, so all current callers (cleanup-then-restore rollback, LSP shutdown, doctor timeout) rely on the awaited promise meaning \"the npm process stopped\" — but on Unix the promise resolves while npm may still be running and writing, so the subsequent directory removal/state restore can race. Make the non-shim path also await the child's `exit` event to match the Windows guarantee.</comment>\n\n<file context>\n@@ -223,21 +227,71 @@ export function npmInvocation(\n+    } catch {\n+      // Best-effort parity with ChildProcess.kill() callers.\n+    }\n+    return Promise.resolve();\n+  }\n+\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`55f0ba8`](https://github.com/cortexkit/aft/commit/55f0ba88a86329e12601376af303ba3148617224)","createdAt":"2026-08-27T16:15:07Z","updatedAt":"2026-08-27T16:59:03Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=75089846-cd18-405d-b4bd-82a75b6eaeb9 -->\n<!-- cubic:review-run=f3dc5e06-9e51-44f9-ba4b-5b77906112be -->\n<!-- metadata:{\"confidence\":6} -->\nP3: The tree-kill test can pass vacuously: the shim runs bare `node -e ...`, and the test never verifies a node.exe descendant actually started before terminating the tree. If `node` is not on PATH in the test env (or fails to resolve), the batch's `node` line errors out without ever scheduling the sentinel write, so the test passes even if `terminateNpmProcessTree` were completely broken. Since this is the regression test proving the cmd.exe -> node.exe descendant is killed, it should confirm the descendant launched before asserting on the sentinel.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/aft-bridge/src/__tests__/npm-resolver.test.ts, line 244:\n\n<comment>The tree-kill test can pass vacuously: the shim runs bare `node -e ...`, and the test never verifies a node.exe descendant actually started before terminating the tree. If `node` is not on PATH in the test env (or fails to resolve), the batch's `node` line errors out without ever scheduling the sentinel write, so the test passes even if `terminateNpmProcessTree` were completely broken. Since this is the regression test proving the cmd.exe -> node.exe descendant is killed, it should confirm the descendant launched before asserting on the sentinel.</comment>\n\n<file context>\n@@ -201,16 +210,46 @@ describe(\"npmInvocation\", () => {\n+          stdio: \"ignore\",\n+          windowsVerbatimArguments: invocation.windowsVerbatimArguments,\n+        });\n+        await new Promise((resolve) => setTimeout(resolve, 100));\n+        await terminateNpmProcessTree(child, invocation);\n+        await new Promise((resolve) => setTimeout(resolve, 1_100));\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`a696a22`](https://github.com/cortexkit/aft/commit/a696a22b73084ddc6f67b81ba89e74607d8bc922)","createdAt":"2026-08-27T16:15:07Z","updatedAt":"2026-08-27T16:43:43Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:42:48Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in a696a22b. The test now launches process.execPath through an explicit environment variable with PATH emptied, waits for and asserts a descendant-started marker, then terminates the tree and verifies the delayed sentinel is never written. Its cleanup also terminates the child if the started assertion fails.","createdAt":"2026-08-27T16:42:48Z","updatedAt":"2026-08-27T16:42:48Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:42:48Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in a696a22b. Doctor now discards npm stdout while continuing to capture bounded stderr, so the child cannot block on an unread stdout pipe.","createdAt":"2026-08-27T16:42:48Z","updatedAt":"2026-08-27T16:42:48Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:45:45Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Fallback leaves descendants running**\n\nIf `taskkill.exe` fails to start or exits nonzero while an npm/node descendant remains alive, `finish(true)` calls `child.kill()` only on the immediate `cmd.exe` process and then resolves. Callers consequently restore shared cache files, release installation locks, or report timeout completion while npm continues writing to `package-lock.json` or `node_modules`.\n\n**Knowledge Base Used:**\n- [Bridge storage and installation](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/bridge-storage-and-installation.md)\n- [LSP integration](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/lsp-integration.md)","createdAt":"2026-08-27T16:45:42Z","updatedAt":"2026-08-27T16:45:45Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:58:17Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 55f0ba88. Windows shim termination now requires positive taskkill /T /F confirmation plus immediate-child exit. taskkill start failure, nonzero exit, or timeout rejects with npm_termination_unknown and never falls back to killing cmd.exe alone. Auto-update skips rollback and preserves its staged snapshot, Doctor surfaces the unknown outcome, and LSP keeps its install lock pending so another install cannot overlap a possibly-live descendant. Added a regression test proving taskkill startup failure does not call child.kill().","createdAt":"2026-08-27T16:58:16Z","updatedAt":"2026-08-27T16:58:17Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T16:58:32Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 5ecf7766. Direct executable termination now resolves only after the ChildProcess exit event and escalates from the normal termination signal to SIGKILL after a bounded five-second grace period. Added regression tests for both the awaited-exit contract and escalation.","createdAt":"2026-08-27T16:58:32Z","updatedAt":"2026-08-27T16:58:32Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 11 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787849895815_676f6fed-249d-4664-92ca-348bf65f88bd?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787849895815_676f6fed-249d-4664-92ca-348bf65f88bd:55f0ba88a86329e12601376af303ba3148617224:bf3885db-fcaa-4a22-b7ad-355582d62f8f -->","state":"COMMENTED","submittedAt":"2026-08-27T17:11:05Z","comments":{"totalCount":7,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=df7cc305-0eb4-4247-99b4-2cb46049f587 -->\n<!-- cubic:review-run=bf3885db-fcaa-4a22-b7ad-355582d62f8f -->\n<!-- metadata:{\"confidence\":9} -->\nP1: When Windows tree termination is unconfirmed, this return lets the auto-update check release its coordination lock while npm descendants may still write. Propagate an explicit blocked outcome so the install lock remains held or requires manual recovery before another update can start.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/hooks/auto-update-checker/cache.ts, line 331:\n\n<comment>When Windows tree termination is unconfirmed, this return lets the auto-update check release its coordination lock while npm descendants may still write. Propagate an explicit blocked outcome so the install lock remains held or requires manual recovery before another update can start.</comment>\n\n<file context>\n@@ -319,7 +320,16 @@ export async function runNpmInstallSafe(\n+        // snapshot and report the unknown outcome for manual recovery/restart.\n+        const reason = `termination outcome unknown: ${String(error)}`;\n+        warnNpmInstallFailure(reason, stderrTail);\n+        return { ok: false, reason, stderrTail: stderrTail || undefined };\n+      }\n       const snapshot = pendingSnapshots.get(installDir);\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`ce113e0`](https://github.com/cortexkit/aft/commit/ce113e027c6694d734bf4348a490e144c3b2c3c1)","createdAt":"2026-08-27T17:11:06Z","updatedAt":"2026-08-27T17:56:41Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=023806ec-8fac-4791-9591-cc1537c267ea -->\n<!-- cubic:review-run=bf3885db-fcaa-4a22-b7ad-355582d62f8f -->\n<!-- metadata:{\"confidence\":9} -->\nP1: When `terminateNpmProcessTree` rejects, this branch leaves the install lock pending, but Windows can still reclaim that lock after 30 minutes without checking the owner. A descendant that survived `taskkill` can therefore overlap a later install and corrupt the shared LSP cache; persist an unknown, non-reclaimable state or coordinate stale-lock recovery with the pending termination.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/pi-plugin/src/lsp-auto-install.ts, line 368:\n\n<comment>When `terminateNpmProcessTree` rejects, this branch leaves the install lock pending, but Windows can still reclaim that lock after 30 minutes without checking the owner. A descendant that survived `taskkill` can therefore overlap a later install and corrupt the shared LSP cache; persist an unknown, non-reclaimable state or coordinate stale-lock recovery with the pending termination.</comment>\n\n<file context>\n@@ -359,17 +364,23 @@ function runInstall(\n-        }\n-      });\n+      void (terminationPromise ?? Promise.resolve()).then(\n+        () => {\n+          if (code === 0) {\n+            log(`[lsp] installed ${target}`);\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`ce113e0`](https://github.com/cortexkit/aft/commit/ce113e027c6694d734bf4348a490e144c3b2c3c1)","createdAt":"2026-08-27T17:11:06Z","updatedAt":"2026-08-27T17:56:40Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=3b7d2560-f3b3-4882-80be-ab0b89ae693f -->\n<!-- cubic:review-run=bf3885db-fcaa-4a22-b7ad-355582d62f8f -->\n<!-- metadata:{\"confidence\":9} -->\nP1: When taskkill fails and an npm descendant remains alive, this branch only leaves the install pending, but Windows lock recovery can reclaim `.aft-installing` after 30 minutes and start a concurrent install against the same cache. It also makes shutdown wait forever through `abortInFlightAutoInstalls()`; persist an unknown-termination state that cannot be reclaimed until termination is confirmed, while allowing shutdown to stop awaiting this promise.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/lsp-auto-install.ts, line 395:\n\n<comment>When taskkill fails and an npm descendant remains alive, this branch only leaves the install pending, but Windows lock recovery can reclaim `.aft-installing` after 30 minutes and start a concurrent install against the same cache. It also makes shutdown wait forever through `abortInFlightAutoInstalls()`; persist an unknown-termination state that cannot be reclaimed until termination is confirmed, while allowing shutdown to stop awaiting this promise.</comment>\n\n<file context>\n@@ -374,17 +379,23 @@ function runInstall(\n+          }\n+        },\n+        () => {\n+          // Keep withInstallLock pending: the npm descendant may still be\n+          // mutating this target, so another install must not overlap it.\n+        },\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`ce113e0`](https://github.com/cortexkit/aft/commit/ce113e027c6694d734bf4348a490e144c3b2c3c1)","createdAt":"2026-08-27T17:11:06Z","updatedAt":"2026-08-27T17:56:38Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=8a824db2-415c-459a-b8ee-8bb75f8b19c3 -->\n<!-- cubic:review-run=bf3885db-fcaa-4a22-b7ad-355582d62f8f -->\n<!-- metadata:{\"confidence\":6} -->\nP1: When a Windows shim termination fails while the cmd.exe child is still alive, the child never emits \"exit\", so runInstall's promise never settles. The resolution handlers only run from the exit event, and the abort path explicitly returns without finishing. Because abortInFlightAutoInstalls() awaits this promise via Promise.allSettled, an unknown-termination outcome can hang the whole host shutdown instead of just retaining the lock. Make the rejection path call finish(false) (or otherwise settle runInstall) after logging, or decouple shutdown from a pending install.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/lsp-auto-install.ts, line 351:\n\n<comment>When a Windows shim termination fails while the cmd.exe child is still alive, the child never emits \"exit\", so runInstall's promise never settles. The resolution handlers only run from the exit event, and the abort path explicitly returns without finishing. Because abortInFlightAutoInstalls() awaits this promise via Promise.allSettled, an unknown-termination outcome can hang the whole host shutdown instead of just retaining the lock. Make the rejection path call finish(false) (or otherwise settle runInstall) after logging, or decouple shutdown from a pending install.</comment>\n\n<file context>\n@@ -348,6 +348,11 @@ function runInstall(\n       warn(`[lsp] install ${target} aborted during shutdown`);\n       if (invocation.windowsCmdShim) {\n         terminationPromise ??= terminateNpmProcessTree(child, invocation);\n+        void terminationPromise.catch((terminationError) => {\n+          error(\n+            `[lsp] install ${target} termination outcome unknown; retaining install lock: ${String(terminationError)}`,\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`ce113e0`](https://github.com/cortexkit/aft/commit/ce113e027c6694d734bf4348a490e144c3b2c3c1)","createdAt":"2026-08-27T17:11:06Z","updatedAt":"2026-08-27T17:56:37Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=6428a76c-a3bd-4b70-b7ff-16b02a3da585 -->\n<!-- cubic:review-run=bf3885db-fcaa-4a22-b7ad-355582d62f8f -->\n<!-- metadata:{\"confidence\":6} -->\nP2: The fail-closed return path in runNpmInstallSafe leaves the pending snapshot in the module-level pendingSnapshots map and never removes its mkdtempSync tempDir, so the staged copy and temp directory leak for the lifetime of the process. Since the whole point is fail-closed recovery, document/queue cleanup (or ensure a re-prepare overwrites and cleans the prior tempDir) rather than leaving an unreachable staged snapshot.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/hooks/auto-update-checker/cache.ts, line 331:\n\n<comment>The fail-closed return path in runNpmInstallSafe leaves the pending snapshot in the module-level pendingSnapshots map and never removes its mkdtempSync tempDir, so the staged copy and temp directory leak for the lifetime of the process. Since the whole point is fail-closed recovery, document/queue cleanup (or ensure a re-prepare overwrites and cleans the prior tempDir) rather than leaving an unreachable staged snapshot.</comment>\n\n<file context>\n@@ -319,7 +320,16 @@ export async function runNpmInstallSafe(\n+        // snapshot and report the unknown outcome for manual recovery/restart.\n+        const reason = `termination outcome unknown: ${String(error)}`;\n+        warnNpmInstallFailure(reason, stderrTail);\n+        return { ok: false, reason, stderrTail: stderrTail || undefined };\n+      }\n       const snapshot = pendingSnapshots.get(installDir);\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`7ae27ff`](https://github.com/cortexkit/aft/commit/7ae27ff2c4e70d08036c66a92a770d5958a70bdf)","createdAt":"2026-08-27T17:11:06Z","updatedAt":"2026-08-29T15:11:26Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=de1e0ac2-f83a-436e-98d2-59b919c4d2f2 -->\n<!-- cubic:review-run=bf3885db-fcaa-4a22-b7ad-355582d62f8f -->\n<!-- metadata:{\"confidence\":5} -->\nP2: When terminateNpmProcessTree runs after the immediate cmd.exe child has already exited (a shutdown/timeout race with a completed install), it still spawns taskkill, which returns a nonzero 'process not found' code for a fully-exited tree. That becomes NpmTerminationUnknownError even though nothing is left to write, and in the LSP callers the rejection branch retains the on-disk install lock indefinitely. Treat a taskkill failure whose PID is absent while the child already exited as a confirmed, successful termination.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/aft-bridge/src/npm-resolver.ts, line 300:\n\n<comment>When terminateNpmProcessTree runs after the immediate cmd.exe child has already exited (a shutdown/timeout race with a completed install), it still spawns taskkill, which returns a nonzero 'process not found' code for a fully-exited tree. That becomes NpmTerminationUnknownError even though nothing is left to write, and in the LSP callers the rejection branch retains the on-disk install lock indefinitely. Treat a taskkill failure whose PID is absent while the child already exited as a confirmed, successful termination.</comment>\n\n<file context>\n@@ -251,44 +251,110 @@ export function npmInvocation(\n-    }\n-    return Promise.resolve();\n+  if (!invocation.windowsCmdShim) return terminateDirectNpmChild(child, gracePeriodMs);\n+  if (child.pid === undefined) {\n+    return Promise.reject(new NpmTerminationUnknownError(\"cmd.exe child has no process ID\"));\n   }\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`5803568`](https://github.com/cortexkit/aft/commit/58035689bc40ae0fbe0138d2ad57d9fe1008293a)","createdAt":"2026-08-27T17:11:06Z","updatedAt":"2026-08-27T20:20:03Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=7f52f9b0-04a7-417e-8cdb-c7f831326386 -->\n<!-- cubic:review-run=bf3885db-fcaa-4a22-b7ad-355582d62f8f -->\n<!-- metadata:{\"confidence\":6} -->\nP3: This test verifies the reason string but never exercises the fail-closed side effect it is meant to guard. It calls runNpmInstallSafe without a pending snapshot (preparePackageUpdate is never called), so it cannot detect a regression where the unknown-termination branch wrongly restores the staged snapshot or releases the install lock. To lock in the documented behavior, create a snapshot first (preparePackageUpdate), make terminateNpmProcessTree reject, then assert the snapshot is still retained in pendingSnapshots / restoreAutoUpdateSnapshot was not invoked.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/hooks/auto-update-checker/cache.test.ts, line 272:\n\n<comment>This test verifies the reason string but never exercises the fail-closed side effect it is meant to guard. It calls runNpmInstallSafe without a pending snapshot (preparePackageUpdate is never called), so it cannot detect a regression where the unknown-termination branch wrongly restores the staged snapshot or releases the install lock. To lock in the documented behavior, create a snapshot first (preparePackageUpdate), make terminateNpmProcessTree reject, then assert the snapshot is still retained in pendingSnapshots / restoreAutoUpdateSnapshot was not invoked.</comment>\n\n<file context>\n@@ -261,6 +261,24 @@ describe(\"auto-update-checker/cache\", () => {\n+      terminateNpmProcessTreeMock.mockRejectedValueOnce(new Error(\"taskkill failed\"));\n+      const { runNpmInstallSafe } = await freshCacheImport();\n+\n+      const result = await runNpmInstallSafe(\"/tmp/opencode\", { timeoutMs: 1 });\n+\n+      expect(result).toMatchObject({\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`ce113e0`](https://github.com/cortexkit/aft/commit/ce113e027c6694d734bf4348a490e144c3b2c3c1)","createdAt":"2026-08-27T17:11:06Z","updatedAt":"2026-08-27T17:56:39Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:55:16Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Addressed in ce113e02 with a bounded session quarantine. The retained pending snapshot now blocks preparePackageUpdate from starting another update in this host session and logs its recovery path, so releasing the coordination check does not permit an in-session retry. We intentionally do not create a permanent cross-process tombstone for the exceptional taskkill-unknown case, because that could lock users out indefinitely.","createdAt":"2026-08-27T17:55:15Z","updatedAt":"2026-08-27T17:55:16Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:55:16Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Addressed in ce113e02. OpenCode now quarantines the affected npm package for the host session and settles runInstall(false), so shutdown can complete while subsequent in-session installs are refused. We deliberately retain the existing bounded cross-process stale-lock recovery rather than introducing a permanent tombstone that would require manual deletion.","createdAt":"2026-08-27T17:55:16Z","updatedAt":"2026-08-27T17:55:16Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:55:16Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in ce113e02. The termination rejection handler now records the session quarantine and calls finish(false), allowing abortInFlightAutoInstalls()/Promise.allSettled to complete. The exit-handler rejection path also settles defensively.","createdAt":"2026-08-27T17:55:16Z","updatedAt":"2026-08-27T17:55:16Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:55:16Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Clarified and guarded in ce113e02. The snapshot is intentionally retained as the only recovery artifact when descendant liveness is unknown; its exact tempDir is now logged, and preparePackageUpdate refuses to overwrite it during the host session. Automatic deletion here would destroy rollback data. The strengthened test verifies retention and retry blocking.","createdAt":"2026-08-27T17:55:16Z","updatedAt":"2026-08-27T17:55:16Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:55:16Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in ce113e02. The test now calls preparePackageUpdate first, forces termination rejection, verifies package.json is not restored, verifies the staged package copy remains, checks that the recovery snapshot path is reported, and proves a second preparePackageUpdate is blocked.","createdAt":"2026-08-27T17:55:16Z","updatedAt":"2026-08-27T17:55:16Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:55:16Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Addressed in ce113e02. Pi records the package in quarantinedNpmInstalls before settling the failed install, and ensureServerInstalled refuses retries for that package for the remainder of the host session. This prevents same-host overlap while avoiding a permanent non-reclaimable disk tombstone; the existing 30-minute cross-process stale-lock policy remains the deliberate bounded recovery policy.","createdAt":"2026-08-27T17:55:16Z","updatedAt":"2026-08-27T17:55:16Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:55:16Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Not changing this behavior. cmd.exe exit is not proof that its node.exe descendant exited; that possibility is the reason tree termination is required. Treating taskkill process-not-found as confirmed solely because the immediate child exited would reintroduce the original rollback/lock-release race. The conservative unknown outcome is intentional.","createdAt":"2026-08-27T17:55:16Z","updatedAt":"2026-08-27T17:55:16Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T17:57:48Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Lock released prematurely**\n\nWhen Windows process-tree termination cannot be confirmed during shutdown, `finish(false)` settles the install task and releases its cross-process lock even though an npm descendant can still be writing. If another Pi or OpenCode process starts, it can acquire that lock and install into the same shared cache concurrently, corrupting `node_modules` or the installed LSP binary.\n\n**Knowledge Base Used:** [LSP integration](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/lsp-integration.md)","createdAt":"2026-08-27T17:57:47Z","updatedAt":"2026-08-27T17:57:48Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 6 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787853301424_df73f0a3-3ef4-4cf6-af04-3c8c6e36c11a?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787853301424_df73f0a3-3ef4-4cf6-af04-3c8c6e36c11a:ce113e027c6694d734bf4348a490e144c3b2c3c1:65abc60b-3f53-4f1e-8d9a-09367481d0fd -->","state":"COMMENTED","submittedAt":"2026-08-27T18:02:23Z","comments":{"totalCount":2,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=853d39f9-50a2-46c0-b734-75c31c731da9 -->\n<!-- cubic:review-run=65abc60b-3f53-4f1e-8d9a-09367481d0fd -->\n<!-- metadata:{\"confidence\":9} -->\nP1: When Windows tree termination is unconfirmed, `finish(false)` releases the cross-process install lock even though the npm descendant may still be modifying the package directory. The in-memory quarantine blocks only this host process, so retain a durable lock/quarantine marker or otherwise coordinate cross-process retries until descendant termination is confirmed.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/lsp-auto-install.ts, line 359:\n\n<comment>When Windows tree termination is unconfirmed, `finish(false)` releases the cross-process install lock even though the npm descendant may still be modifying the package directory. The in-memory quarantine blocks only this host process, so retain a durable lock/quarantine marker or otherwise coordinate cross-process retries until descendant termination is confirmed.</comment>\n\n<file context>\n@@ -349,9 +352,11 @@ function runInstall(\n-            `[lsp] install ${target} termination outcome unknown; retaining install lock: ${String(terminationError)}`,\n+            `[lsp] install ${target} termination outcome unknown; quarantining retries for this session: ${String(terminationError)}`,\n           );\n+          finish(false);\n         });\n         return;\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`5ab9904`](https://github.com/cortexkit/aft/commit/5ab9904b77e3d641bbf5059268b6a7f8cc9bc698)","createdAt":"2026-08-27T18:02:23Z","updatedAt":"2026-08-27T18:44:36Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=56969734-0728-498e-9f56-b62d358fc85f -->\n<!-- cubic:review-run=65abc60b-3f53-4f1e-8d9a-09367481d0fd -->\n<!-- metadata:{\"confidence\":8} -->\nP2: A pre-spawn cancellation can leave `pendingSnapshots` populated, so this guard misclassifies it as an unconfirmed npm termination and blocks every later update in the session. Clean up the staged snapshot on every pre-spawn exit, or track confirmed quarantine state separately from merely pending snapshots.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/hooks/auto-update-checker/cache.ts, line 207:\n\n<comment>A pre-spawn cancellation can leave `pendingSnapshots` populated, so this guard misclassifies it as an unconfirmed npm termination and blocks every later update in the session. Clean up the staged snapshot on every pre-spawn exit, or track confirmed quarantine state separately from merely pending snapshots.</comment>\n\n<file context>\n@@ -204,6 +204,15 @@ export function preparePackageUpdate(\n       return null;\n     }\n \n+    const pendingSnapshot = pendingSnapshots.get(installContext.installDir);\n+    if (pendingSnapshot) {\n+      warn(\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`5ab9904`](https://github.com/cortexkit/aft/commit/5ab9904b77e3d641bbf5059268b6a7f8cc9bc698)","createdAt":"2026-08-27T18:02:23Z","updatedAt":"2026-08-27T18:44:37Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T18:43:38Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 5ab9904b. Unknown Windows tree termination now calls `lock.retain()` before the install promise settles, so the existing cross-process lock file remains in place instead of being deleted by `finish(false)`. The established stale-lock policy bounds that protection to 30 minutes; session-local quarantine still prevents retries in the current host. Added regression coverage proving the retained lock blocks a second lock acquisition.","createdAt":"2026-08-27T18:43:38Z","updatedAt":"2026-08-27T18:43:38Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T18:43:38Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 5ab9904b. `quarantinedInstallDirs` is now separate from `pendingSnapshots`, and `preparePackageUpdate()` checks only explicit quarantine state. Pre-spawn cancellation or npm-resolution failure restores and removes the staged snapshot, while quarantine is set only for `npm_termination_unknown`. Added regressions for both pre-spawn cleanup and unknown-termination refusal.","createdAt":"2026-08-27T18:43:38Z","updatedAt":"2026-08-27T18:43:38Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T18:43:38Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in 5ab9904b. Unknown Windows tree termination now retains the existing filesystem install lock before settling, preserving cross-process exclusion for the lock's bounded 30-minute stale period. Session-local quarantine remains as the immediate same-host guard, and regression tests cover the retained lock behavior.","createdAt":"2026-08-27T18:43:38Z","updatedAt":"2026-08-27T18:43:38Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 12 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787856205846_397d4487-2bd9-4857-9427-643e36ea790e?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787856205846_397d4487-2bd9-4857-9427-643e36ea790e:5ab9904b77e3d641bbf5059268b6a7f8cc9bc698:f753768d-88e5-4f99-af59-07dd2fa6c2cd -->","state":"COMMENTED","submittedAt":"2026-08-27T18:47:36Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=0edf496b-55b7-4247-b3da-5d738ef8a759 -->\n<!-- cubic:review-run=f753768d-88e5-4f99-af59-07dd2fa6c2cd -->\n<!-- metadata:{\"confidence\":8} -->\nP1: After stale recovery creates a second same-key lock in the same process, this key-only marker can be consumed by the wrong lock holder. The older task can then unlink the newer task’s lock because both generations share the same PID, allowing overlapping installs into the cache. Associate retention and release with a unique lock-acquisition token rather than only `lockKey` and `process.pid`.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/opencode-plugin/src/lsp-cache.ts, line 168:\n\n<comment>After stale recovery creates a second same-key lock in the same process, this key-only marker can be consumed by the wrong lock holder. The older task can then unlink the newer task’s lock because both generations share the same PID, allowing overlapping installs into the cache. Associate retention and release with a unique lock-acquisition token rather than only `lockKey` and `process.pid`.</comment>\n\n<file context>\n@@ -165,6 +165,7 @@ function lockPath(npmPackage: string): string {\n }\n \n const STALE_LOCK_MS = 30 * 60 * 1000;\n+const retainedInstallLocks = new Set<string>();\n \n /**\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`5803568`](https://github.com/cortexkit/aft/commit/58035689bc40ae0fbe0138d2ad57d9fe1008293a)","createdAt":"2026-08-27T18:47:36Z","updatedAt":"2026-08-27T20:20:04Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T18:48:41Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Clean exit becomes unknown**\n\nWhen a Windows npm shim exits successfully as its timeout or shutdown abort fires, `terminateNpmProcessTree` still invokes `taskkill` for the exited PID. The resulting nonzero status is treated as an unknown termination outcome, causing a completed auto-update to be quarantined for the session or an LSP install lock to remain held until stale-lock recovery.\n\n**Knowledge Base Used:** [LSP integration](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/lsp-integration.md)","createdAt":"2026-08-27T18:48:40Z","updatedAt":"2026-08-27T18:48:41Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T20:22:41Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Clean exit misclassified**\n\nWhen the Windows npm shim exits successfully while `taskkill.exe` remains pending past the grace period, this callback rejects without applying the successful-exit check used by the other completion paths, causing callers to quarantine auto-update or retain an LSP installation lock even though npm completed.\n\n```suggestion\n    const timeout = setTimeout(() => {\n      if (exitedSuccessfully()) {\n        settled = true;\n        cleanup();\n        resolve();\n        return;\n      }\n      try {\n        killer?.kill();\n      } catch {\n        // The taskkill process may already have exited.\n      }\n      fail(treeKillFailure ?? `taskkill.exe did not finish within ${gracePeriodMs}ms`);\n    }, gracePeriodMs);\n```\n\n**Knowledge Base Used:**\n- [Bridge storage and installation](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/bridge-storage-and-installation.md)\n- [LSP integration](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/lsp-integration.md)","createdAt":"2026-08-27T20:22:39Z","updatedAt":"2026-08-27T20:22:41Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 11 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787861977159_7ac39163-033a-48b7-988e-ac1e18a6bda5?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787861977159_7ac39163-033a-48b7-988e-ac1e18a6bda5:58035689bc40ae0fbe0138d2ad57d9fe1008293a:8dae309b-7bb7-43a5-b225-021dfe82af05 -->","state":"COMMENTED","submittedAt":"2026-08-27T20:27:01Z","comments":{"totalCount":2,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=6cac327e-ee21-44c4-8ef8-fe181c120de3 -->\n<!-- cubic:review-run=8dae309b-7bb7-43a5-b225-021dfe82af05 -->\n<!-- metadata:{\"confidence\":8} -->\nP1: When the Windows child emits `error` before the termination promise rejects, `withInstallLock` releases this lease before `retain()` runs, so unknown termination does not retain the cross-process lock. Await termination before resolving the install on every path, or make retention coordinate with release.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/pi-plugin/src/lsp-cache.ts, line 191:\n\n<comment>When the Windows child emits `error` before the termination promise rejects, `withInstallLock` releases this lease before `retain()` runs, so unknown termination does not retain the cross-process lock. Await termination before resolving the install on every path, or make retention coordinate with release.</comment>\n\n<file context>\n@@ -158,33 +163,53 @@ const retainedInstallLocks = new Set<string>();\n+          retained = true;\n+        },\n+        release() {\n+          if (released) return;\n+          released = true;\n+          if (retained) {\n</file context>\n```\n\n</details>","createdAt":"2026-08-27T20:27:02Z","updatedAt":"2026-08-27T20:27:02Z","isMinimized":false},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=e0fc5d51-883e-4965-86ab-3b84d4566a59 -->\n<!-- cubic:review-run=8dae309b-7bb7-43a5-b225-021dfe82af05 -->\n<!-- metadata:{\"confidence\":8} -->\nP2: When the cmd child completes just before its queued `exit` event, this grace timer can still observe `exitCode === null` and classify the clean completion as unknown. Check `exitedSuccessfully()` before calling `fail` here so completed installs are not quarantined or left locked.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/aft-bridge/src/npm-resolver.ts, line 321:\n\n<comment>When the cmd child completes just before its queued `exit` event, this grace timer can still observe `exitCode === null` and classify the clean completion as unknown. Check `exitedSuccessfully()` before calling `fail` here so completed installs are not quarantined or left locked.</comment>\n\n<file context>\n@@ -305,14 +310,15 @@ export function terminateNpmProcessTree(\n         // The taskkill process may already have exited.\n       }\n-      fail(`taskkill.exe did not finish within ${gracePeriodMs}ms`);\n+      fail(treeKillFailure ?? `taskkill.exe did not finish within ${gracePeriodMs}ms`);\n     }, gracePeriodMs);\n     const cleanup = () => {\n</file context>\n```\n\n</details>\n\n```suggestion\n      if (exitedSuccessfully()) {\n        settled = true;\n        cleanup();\n        resolve();\n      } else {\n        fail(treeKillFailure ?? `taskkill.exe did not finish within ${gracePeriodMs}ms`);\n      }\n```\n\n✅ Addressed in [`ce466e1`](https://github.com/cortexkit/aft/commit/ce466e19d677a1d27e58342eff9da84743bf9368)","createdAt":"2026-08-27T20:27:02Z","updatedAt":"2026-08-27T20:38:24Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T20:38:30Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in ce466e19. The grace-period callback now stops the pending taskkill helper, then checks the authoritative child state before returning npm_termination_unknown. A child with exitCode 0 and no signal resolves successfully instead of triggering quarantine or lock retention. Added a regression with a deliberately hanging fake taskkill executable.","createdAt":"2026-08-27T20:38:30Z","updatedAt":"2026-08-27T20:38:30Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T20:38:32Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Addressed in ce466e19. At the termination deadline we now accept only an observed clean cmd exit (exitCode === 0 and signalCode === null); an unobserved/null exit status remains unknown. The hanging-taskkill regression covers the concrete clean-completion case without weakening fail-closed handling.","createdAt":"2026-08-27T20:38:32Z","updatedAt":"2026-08-27T20:38:32Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-27T20:38:34Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"No change for this finding. In this Windows spawn path the ChildProcess has no AbortSignal, child.kill is never called, and child.send is never used, so its error event denotes spawn failure. A failed spawn has no npm descendant that could still mutate the cache; releasing the install lock is therefore safe. Retaining it after the no-PID termination rejection would create an unnecessary 30-minute stale lock, not prevent an overlap.","createdAt":"2026-08-27T20:38:34Z","updatedAt":"2026-08-27T20:38:34Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T15:31:25Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Confirmed taskkill becomes unknown**\n\nWhen `taskkill.exe` exits successfully but the `cmd.exe` child has not emitted its exit event before the grace timer expires, the timeout callback ignores `treeKillConfirmed` and reports an unknown termination. This unnecessarily quarantines the auto-update state or retains the LSP installation lock until stale recovery even though the process tree was successfully terminated.\n\n**Knowledge Base Used:** [LSP integration](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/lsp-integration.md)","createdAt":"2026-08-28T15:31:24Z","updatedAt":"2026-08-28T15:31:25Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**1 issue found** across 4 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<details>\n<summary>Prompt for AI agents (unresolved issues)</summary>\n\n```text\n\nCheck if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.\n\n\n<file name=\"docs/v0.49-agent-prefix-capture.json\">\n\n<violation number=\"1\" location=\"docs/v0.49-agent-prefix-capture.json:13\">\nP3: The re-anchor downgrades the opencode captures' recorded host_version from a verified \"1.18.18\" to null, because opencode is not installed in the regeneration environment (the script's hostVersion() fail-closes to null, while pi resolves 0.84.3). This audit artifact is an evidence ledger, and the previous commit recorded a concrete, verified opencode version that this change silently drops. Confirm this regression to unknown is intended; if opencode 1.18.18 is the version the releases run against, consider capturing the version explicitly (or noting the environment limitation) instead of letting the artifact lose the previously recorded value.</violation>\n</file>\n```\n\n</details>\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787930892249_c9996b13-2a0e-46ed-8e6e-8ffcada2184f?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787930892249_c9996b13-2a0e-46ed-8e6e-8ffcada2184f:e9f2a241ecf14355fc2884fbefb0a87169e1b7fa:31b7027c-07a5-4531-9502-9cb7639d8e4b -->","state":"COMMENTED","submittedAt":"2026-08-28T15:33:42Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=42195dc2-4e81-4809-843a-8ccc8f1cf88c -->\n<!-- cubic:review-run=31b7027c-07a5-4531-9502-9cb7639d8e4b -->\n<!-- metadata:{\"confidence\":6} -->\nP3: The re-anchor downgrades the opencode captures' recorded host_version from a verified \"1.18.18\" to null, because opencode is not installed in the regeneration environment (the script's hostVersion() fail-closes to null, while pi resolves 0.84.3). This audit artifact is an evidence ledger, and the previous commit recorded a concrete, verified opencode version that this change silently drops. Confirm this regression to unknown is intended; if opencode 1.18.18 is the version the releases run against, consider capturing the version explicitly (or noting the environment limitation) instead of letting the artifact lose the previously recorded value.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At docs/v0.49-agent-prefix-capture.json, line 13:\n\n<comment>The re-anchor downgrades the opencode captures' recorded host_version from a verified \"1.18.18\" to null, because opencode is not installed in the regeneration environment (the script's hostVersion() fail-closes to null, while pi resolves 0.84.3). This audit artifact is an evidence ledger, and the previous commit recorded a concrete, verified opencode version that this change silently drops. Confirm this regression to unknown is intended; if opencode 1.18.18 is the version the releases run against, consider capturing the version explicitly (or noting the environment limitation) instead of letting the artifact lose the previously recorded value.</comment>\n\n<file context>\n@@ -1,24 +1,24 @@\n-        \"value\": \"1.18.18\",\n-        \"method\": \"opencode --version\"\n+        \"value\": null,\n+        \"method\": \"opencode --version (not available in capture environment)\"\n       },\n       \"capture_method\": \"production buildOpenCodeToolMap registration output plus system-transform workflow hint input\",\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`b11d3b3`](https://github.com/cortexkit/aft/commit/b11d3b3fa22376dfe72fe76fb855cf5da9360fc0)","createdAt":"2026-08-28T15:33:42Z","updatedAt":"2026-08-28T16:00:12Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T15:59:59Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in a133943f. The grace-deadline path now treats a successful taskkill /T /F result (`treeKillConfirmed`) as confirmed termination even if Node has not yet delivered the cmd.exe exit event. Start failures, nonzero exits, and taskkill timeouts still fail closed. Added a regression with a successful fake taskkill and a deliberately lagging child exit state.","createdAt":"2026-08-28T15:59:59Z","updatedAt":"2026-08-28T15:59:59Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T16:00:01Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in b11d3b3f. I regenerated the LF audit artifacts with the exact `opencode-ai@1.18.18` executable available on PATH, so all OpenCode profiles retain the verified 1.18.18 host version, then re-anchored the manifest. The version remains captured from `opencode --version`; it was not hardcoded. The audit passes.","createdAt":"2026-08-28T16:00:01Z","updatedAt":"2026-08-28T16:00:01Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 4 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787932790505_805bc1a8-aa6d-4bb2-87e4-8fef9173b648?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787932790505_805bc1a8-aa6d-4bb2-87e4-8fef9173b648:b11d3b3fa22376dfe72fe76fb855cf5da9360fc0:b86ab03a-45bd-4528-a4eb-edd21cc7a2b2 -->","state":"COMMENTED","submittedAt":"2026-08-28T16:05:15Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=82004926-0675-4785-bbb9-95513ef81a6c -->\n<!-- cubic:review-run=b86ab03a-45bd-4528-a4eb-edd21cc7a2b2 -->\n<!-- metadata:{\"confidence\":6} -->\nP3: This test's pass/fail depends on the real fake taskkill subprocess completing within the 25ms grace period. The child never emits `exit` here, so the only way to settle is the grace-timeout path in `terminateNpmProcessTree`, which resolves only if `treeKillConfirmed` is already true; if the spawned `taskkill.exe` takes longer than 25ms to fork and exit (common under CI load), `treeKillConfirmed` is false, `exitedSuccessfully()` is false (exitCode is null), and the promise rejects with `NpmTerminationUnknownError`, failing the test. Unlike the sibling \"taskkill remains pending\" test (which settles via `exitedSuccessfully()` at the deadline), this one is uniquely time-sensitive, so it can flake. Use a grace period comfortably above realistic spawn latency (e.g. 1_000ms) so the confirmed-taskkill branch is deterministically exercised after the kill has completed.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At packages/aft-bridge/src/__tests__/npm-resolver.test.ts, line 352:\n\n<comment>This test's pass/fail depends on the real fake taskkill subprocess completing within the 25ms grace period. The child never emits `exit` here, so the only way to settle is the grace-timeout path in `terminateNpmProcessTree`, which resolves only if `treeKillConfirmed` is already true; if the spawned `taskkill.exe` takes longer than 25ms to fork and exit (common under CI load), `treeKillConfirmed` is false, `exitedSuccessfully()` is false (exitCode is null), and the promise rejects with `NpmTerminationUnknownError`, failing the test. Unlike the sibling \"taskkill remains pending\" test (which settles via `exitedSuccessfully()` at the deadline), this one is uniquely time-sensitive, so it can flake. Use a grace period comfortably above realistic spawn latency (e.g. 1_000ms) so the confirmed-taskkill branch is deterministically exercised after the kill has completed.</comment>\n\n<file context>\n@@ -327,6 +327,38 @@ describe(\"npmInvocation\", () => {\n+          terminateNpmProcessTree(\n+            child,\n+            { command: \"cmd.exe\", args: [], windowsCmdShim: true },\n+            { SystemRoot: systemRoot },\n+            25,\n+          ),\n</file context>\n```\n\n</details>\n\n```suggestion\n            { SystemRoot: systemRoot },\n            1_000,\n          ),\n```\n\n✅ Addressed in [`c232082`](https://github.com/cortexkit/aft/commit/c2320828a2a98083a82af5cd41b5559e801cdbd7)","createdAt":"2026-08-28T16:05:16Z","updatedAt":"2026-08-28T17:04:43Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T17:06:39Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Confirmed termination leaves lock held**\n\nWhen `taskkill.exe` confirms successful process-tree termination but the `cmd.exe` exit event is delayed, `terminateNpmProcessTree()` resolves without causing `runInstall()` to finish. The install promise remains pending and keeps the cross-process installation lock held, preventing later processes from installing or updating that language server.\n\n**Knowledge Base Used:** [LSP integration](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/lsp-integration.md)","createdAt":"2026-08-28T17:06:38Z","updatedAt":"2026-08-28T17:06:39Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T17:31:20Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in e4e8d047. The finding was valid as a liveness issue (not a process-safety issue): once `taskkill /T /F` confirms the tree is gone, both LSP adapters now settle `runInstall(false)` directly from the termination promise instead of waiting for a potentially delayed child `exit` event. Rejected/unknown termination still retains the token-owned lock and quarantines retries exactly as before. The exit handler now ignores raced wrapper exits after Windows termination starts, preventing a false successful-install log. Added symmetric focused regression assertions for OpenCode and Pi; both focused tests, both typechecks, Biome, and the LF exact-byte audit pass.","createdAt":"2026-08-28T17:31:20Z","updatedAt":"2026-08-28T17:31:20Z","isMinimized":false}]}},{"author":{"login":"greptile-apps"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T21:16:53Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"greptile-apps"},"body":"<a href=\"#\"><img alt=\"P1\" src=\"https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9\" align=\"top\"></a> **Successful install becomes failure**\n\nWhen npm exits successfully immediately before a Windows shutdown abort, `terminateNpmProcessTree()` resolves through its successful-exit branch, but this callback still calls `finish(false)` and the later exit handler is suppressed because `terminationPromise` is set. The completed installation is therefore reported as failed, so its installed-version metadata is omitted and a later session reinstalls it or continues treating the language server as unavailable; the same sequence exists in the Pi adapter.\n\n**Knowledge Base Used:** [LSP integration](https://app.greptile.com/cortexkit/-/custom-context/knowledge-base/cortexkit/aft/-/docs/lsp-integration.md)","createdAt":"2026-08-28T21:16:52Z","updatedAt":"2026-08-28T21:16:53Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T21:38:20Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Fixed in ac26fa29. Both LSP adapters now classify the child's authoritative exit state after confirmed termination: a clean `exitCode === 0` / no-signal completion settles the install as successful and records installed-version metadata, while an actually terminated install settles false. The exit callback is idempotently suppressed once abort settlement owns the outcome.\n\nI also audited the adjacent npm cancellation paths and closed three concrete lifecycle gaps in the same commit: direct-child termination now fails closed after a bounded post-SIGKILL confirmation window, auto-update models abort as an explicit Promise.race outcome so a later abort cannot overwrite an earlier successful exit, and doctor ignores child errors while awaited termination owns settlement. Focused regressions, all four affected package typechecks, Biome, and the exact-byte audit pass.","createdAt":"2026-08-28T21:38:20Z","updatedAt":"2026-08-28T21:38:20Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 2 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787953084590_df9f404f-76fe-46d5-af70-6c6d496d0f10?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787953084590_df9f404f-76fe-46d5-af70-6c6d496d0f10:23ec9749c6a819bcb4e8e99e3828700909903ff3:0e03c462-8722-4d68-bfd2-e4035d2ad73d -->","state":"COMMENTED","submittedAt":"2026-08-28T21:45:49Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=97272e3d-b70a-4f38-903a-9aae56f6ca28 -->\n<!-- cubic:review-run=0e03c462-8722-4d68-bfd2-e4035d2ad73d -->\n<!-- metadata:{\"confidence\":9} -->\nP2: The re-anchor sets source_commit to 1705aaaa for every artifact but leaves the recorded sha256/byte_length values stale, so the manifest no longer matches the actual files at that commit. I hashed the repo files and nearly all artifacts mismatch (e.g. opencode hoisted.ts is recorded as 50427 bytes/sha 0775983e... but is 49224 bytes/sha 939b6f8e...); only legacy-vocabulary and agent-prefix-capture still match. Because `bun scripts/audit-v049-agent-surface.ts` runs `verifyManifest()` at the end and throws on any mismatch, audit verification now fails and the manifest is incorrect for every source surface it claims to pin. Regenerate it (e.g. `bun scripts/audit-v049-agent-surface.ts --write-allowlist --write-prefix-capture --write-manifest`) after the tool-file changes so all artifact hashes reflect the re-anchored source_commit.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At docs/v0.49-agent-surface-manifest.json, line 6:\n\n<comment>The re-anchor sets source_commit to 1705aaaa for every artifact but leaves the recorded sha256/byte_length values stale, so the manifest no longer matches the actual files at that commit. I hashed the repo files and nearly all artifacts mismatch (e.g. opencode hoisted.ts is recorded as 50427 bytes/sha 0775983e... but is 49224 bytes/sha 939b6f8e...); only legacy-vocabulary and agent-prefix-capture still match. Because `bun scripts/audit-v049-agent-surface.ts` runs `verifyManifest()` at the end and throws on any mismatch, audit verification now fails and the manifest is incorrect for every source surface it claims to pin. Regenerate it (e.g. `bun scripts/audit-v049-agent-surface.ts --write-allowlist --write-prefix-capture --write-manifest`) after the tool-file changes so all artifact hashes reflect the re-anchored source_commit.</comment>\n\n<file context>\n@@ -3,7 +3,7 @@\n   \"artifact_version\": \"0.49.0\",\n   \"manifest_id\": \"MAN-V049-S5-AGENT-SURFACE-001\",\n-  \"source_commit\": \"ac26fa29f4cfa15d485b56104ae94edb860808fa\",\n+  \"source_commit\": \"1705aaaa6672988ded84898fb4beeabe20d1a822\",\n   \"source_inventory\": \"docs/v0.49-agent-surface-sources.json\",\n   \"hash_rule\": \"Hash exact UTF-8 file bytes from the source commit; do not normalize newlines, reserialize JSON, or apply test-only normalization.\",\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`6704a20`](https://github.com/cortexkit/aft/commit/6704a20edb50752e0a890756cbdc1831034deef3)","createdAt":"2026-08-28T21:45:49Z","updatedAt":"2026-08-28T22:59:02Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-28T22:58:44Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Valid. The artifacts had been regenerated, but from a Windows CRLF working tree while `source_commit` promises exact Git-object bytes. That made local Windows verification pass while the manifest hashes disagreed with the LF bytes stored at the referenced commit.\n\nFixed in 6704a20e after merging current upstream v0.54.0. I regenerated the allowlist, prefix capture, and manifest from a temporary `core.autocrlf=false` worktree at merge commit 7a284761, verified the audit inside that LF worktree, then independently hashed all 22 non-generated manifest entries from `git show <source_commit>:<path>` (zero byte-length/SHA mismatches). A second clean LF worktree at the final artifact commit also passes `audit-v049-agent-surface.ts`.","createdAt":"2026-08-28T22:58:44Z","updatedAt":"2026-08-28T22:58:44Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**1 issue found** across 2 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<details>\n<summary>Prompt for AI agents (unresolved issues)</summary>\n\n```text\n\nCheck if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.\n\n\n<file name=\"docs/v0.49-agent-surface-manifest.json\">\n\n<violation number=\"1\" location=\"docs/v0.49-agent-surface-manifest.json:355\">\nP2: The prefix-capture artifact's declared sha256 does not match the file bytes at the declared source_commit. The manifest sets source_commit=7a28476 and hash_rule says to hash exact bytes from that commit, but docs/v0.49-agent-prefix-capture.json at 7a28476 hashes to de3b751... (the old value), not the declared d1862df...  Only the regeneration commit 6704a20 contains the declared bytes. Set source_commit (manifest-header and each artifact) to 6704a20, or regenerate with HEAD at the commit that holds these LF bytes, so the referenced commit actually stores every declared hash.</violation>\n</file>\n```\n\n</details>\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1787957919802_50a8694d-9161-487b-a27f-115452ad5746?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1787957919802_50a8694d-9161-487b-a27f-115452ad5746:6704a20edb50752e0a890756cbdc1831034deef3:4cfd59f6-3fdb-43e8-8b36-4713f91d6c3b -->","state":"COMMENTED","submittedAt":"2026-08-28T23:03:19Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=2c4e421c-f3fe-4a85-9803-d53f361fbed3 -->\n<!-- cubic:review-run=4cfd59f6-3fdb-43e8-8b36-4713f91d6c3b -->\n<!-- metadata:{\"confidence\":8} -->\nP2: The prefix-capture artifact's declared sha256 does not match the file bytes at the declared source_commit. The manifest sets source_commit=7a28476 and hash_rule says to hash exact bytes from that commit, but docs/v0.49-agent-prefix-capture.json at 7a28476 hashes to de3b751... (the old value), not the declared d1862df...  Only the regeneration commit 6704a20 contains the declared bytes. Set source_commit (manifest-header and each artifact) to 6704a20, or regenerate with HEAD at the commit that holds these LF bytes, so the referenced commit actually stores every declared hash.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At docs/v0.49-agent-surface-manifest.json, line 355:\n\n<comment>The prefix-capture artifact's declared sha256 does not match the file bytes at the declared source_commit. The manifest sets source_commit=7a28476 and hash_rule says to hash exact bytes from that commit, but docs/v0.49-agent-prefix-capture.json at 7a28476 hashes to de3b751... (the old value), not the declared d1862df...  Only the regeneration commit 6704a20 contains the declared bytes. Set source_commit (manifest-header and each artifact) to 6704a20, or regenerate with HEAD at the commit that holds these LF bytes, so the referenced commit actually stores every declared hash.</comment>\n\n<file context>\n@@ -349,10 +349,10 @@\n       \"encoding\": \"UTF-8\",\n       \"byte_length\": 248998,\n-      \"sha256\": \"de3b75140ced561e455b01141045e1e4e5fe7c582ee69681f86276a21841aba7\"\n+      \"sha256\": \"d1862dfa1e7c1ac408c6d9c6d89ac4dd0737b3c2ab66045e70528f8e5b8d0bfa\"\n     }\n   ]\n</file context>\n```\n\n</details>\n\n✅ Addressed in [`f027919`](https://github.com/cortexkit/aft/commit/f02791933de9310d80f59504005a2bf8f648a8fe)","createdAt":"2026-08-28T23:03:19Z","updatedAt":"2026-08-29T02:54:52Z","isMinimized":false}]}},{"author":{"login":"TreyThomasCodes"},"body":"","state":"COMMENTED","submittedAt":"2026-08-29T02:54:45Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"TreyThomasCodes"},"body":"Valid. The root cause was regenerating the prefix capture and manifest in the same commit-preparation pass: the manifest referenced the pre-generation commit, while the newly generated prefix bytes did not exist until the following artifact commit.\n\nFixed after merging the latest upstream changes. The artifacts were generated from LF bytes at merge commit e444859e and committed in 0e0d3a6b; the manifest was then regenerated by itself and committed in f0279193, so its declared source_commit is 0e0d3a6b—the commit that actually stores the captured prefix bytes. I independently checked all 23 manifest entries with `git cat-file blob <source_commit>:<path>` and got zero byte-length/SHA-256 mismatches. A fresh core.autocrlf=false worktree at f0279193 also passes the full v0.49 audit.","createdAt":"2026-08-29T02:54:45Z","updatedAt":"2026-08-29T02:54:45Z","isMinimized":false}]}},{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:review-summary:start -->\n**All reported issues were addressed** across 3 files (changes from recent commits).\n<!-- cubic:review-summary:end -->\n\n<sub>**Tip**: Review your code locally with the [cubic CLI](https://docs.cubic.dev/ide/cli-review?utm_source=github&utm_content=general_review_body) to iterate faster.<br /><br />[Re-trigger cubic](https://www.cubic.dev/action/re-review/pr/cortexkit/aft/270/ai_pr_review_1788016272941_4216ba21-d4a2-48ae-bd8b-3131be6c6382?returnTo=https%3A%2F%2Fgithub.com%2Fcortexkit%2Faft%2Fpull%2F270)</sub>\n\n<!-- cubic:review-post:ai_pr_review_1788016272941_4216ba21-d4a2-48ae-bd8b-3131be6c6382:7ae27ff2c4e70d08036c66a92a770d5958a70bdf:54dc4c58-e0fe-4b65-86d7-8f9a29ae432c -->","state":"COMMENTED","submittedAt":"2026-08-29T15:18:33Z","comments":{"totalCount":1,"nodes":[{"author":{"login":"cubic-dev-ai"},"body":"<!-- cubic:v=1fe48a74-ed4d-4c3f-8e6e-d47a0b8f5842 -->\n<!-- cubic:review-run=54dc4c58-e0fe-4b65-86d7-8f9a29ae432c -->\n<!-- metadata:{\"confidence\":9} -->\nP2: The prefix-capture artifact's sha256 does not match its declared source_commit. The file at f54058c6... hashes to 44013320d5..., not the 111b6cf4... recorded in the manifest (the 111b6cf4 value matches only the later HEAD byte state, where the capture's embedded source_commit was bumped). Because verifyManifest requires every artifact to share the manifest source_commit (commits.size !== 1), either regenerate the prefix-capture so its f54058c-embedded bytes actually live at the referenced commit, or regenerate the whole manifest against the commit that contains the regenerated capture bytes (e.g. the current head), recomputing all artifact hashes there. As written, verification per the hash_rule fails for this artifact.\n\n<details>\n<summary>Prompt for AI agents</summary>\n\n```text\nCheck if this issue is valid — if so, understand the root cause and fix it. At docs/v0.49-agent-surface-manifest.json, line 355:\n\n<comment>The prefix-capture artifact's sha256 does not match its declared source_commit. The file at f54058c6... hashes to 44013320d5..., not the 111b6cf4... recorded in the manifest (the 111b6cf4 value matches only the later HEAD byte state, where the capture's embedded source_commit was bumped). Because verifyManifest requires every artifact to share the manifest source_commit (commits.size !== 1), either regenerate the prefix-capture so its f54058c-embedded bytes actually live at the referenced commit, or regenerate the whole manifest against the commit that contains the regenerated capture bytes (e.g. the current head), recomputing all artifact hashes there. As written, verification per the hash_rule fails for this artifact.</comment>\n\n<file context>\n@@ -349,10 +349,10 @@\n       \"encoding\": \"UTF-8\",\n       \"byte_length\": 248998,\n-      \"sha256\": \"44013320d5aeeb6a936b32e332e0a6478dfea23c2600521320439f6cea63233c\"\n+      \"sha256\": \"111b6cf4a78c079730e9761a512b5af6273617fa9a26e0013cb253358ef2fed5\"\n     }\n   ]\n</file context>\n```\n\n</details>","createdAt":"2026-08-29T15:18:33Z","updatedAt":"2026-08-29T15:18:34Z","isMinimized":false}]}}]}}}}}