{"record":{"id":"9f3c3c146dd17878","repo":"stablyai/orca","slug":"watcher-supervisor-leaked-its-canary-directory","errorCode":null,"errorMessage":"Watcher supervisor leaked its canary directory: ${watcherCanaryDir}","messagePattern":"Watcher supervisor leaked its canary directory: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"config/scripts/runtime-file-watcher-fault-harness.mjs","lineNumber":176,"sourceCode":"        postCrashEventDelivered: true\n      })\n    )\n  } finally {\n    try {\n      await subscription?.unsubscribe()\n    } finally {\n      supervisor?.dispose()\n      await Promise.all([\n        createdRootPath ? rm(createdRootPath, { recursive: true, force: true }) : Promise.resolve(),\n        rootPath && rootPath !== createdRootPath\n          ? rm(rootPath, { recursive: true, force: true })\n          : Promise.resolve(),\n        bundleDir ? rm(bundleDir, { recursive: true, force: true }) : Promise.resolve()\n      ])\n    }\n  }\n  if (watcherCanaryDir && existsSync(watcherCanaryDir)) {\n    throw new Error(`Watcher supervisor leaked its canary directory: ${watcherCanaryDir}`)\n  }\n}\n\nawait main()\n","sourceCodeStart":158,"sourceCodeEnd":181,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/runtime-file-watcher-fault-harness.mjs#L158-L181","documentation":"The harness creates a canary temp directory (watcherCanaryDir) that the supervisor's dispose() is expected to clean up. After main()'s finally block runs dispose + rm of the test roots, the script checks existsSync(watcherCanaryDir); if the dir is still present, the supervisor leaked it and the run fails. This is a resource-leak invariant enforced outside the try/finally so it runs even on prior failures.","triggerScenarios":"WatcherProcessSupervisor.dispose() did not remove the canary dir it created during setup — e.g. it tracks the dir but skips deletion, throws during cleanup, or the dir path it deletes differs from the one recorded.","commonSituations":"A refactor of the supervisor that dropped canary cleanup; dispose() throwing before the rm step; the canary dir path being changed (different tmp prefix) so the recorded path no longer matches what dispose deletes; dispose() never called because supervisor construction itself failed partway.","solutions":["Ensure WatcherProcessSupervisor.dispose() removes the canary dir it created (mirror the creation path exactly).","Verify the harness records watcherCanaryDir from the same variable the supervisor uses to create it.","If dispose() can throw, wrap its cleanup so the canary rm always runs; the harness's own finally should also rm it as a backstop.","Run with a clean /tmp and re-test — a stale dir from a prior crashed run can also trigger this; ensure the harness uses mkdtemp so names are unique."],"exampleFix":"// before\n// supervisor.dispose() does not remove canary dir\n// after (in WatcherProcessSupervisor)\ndispose() {\n  // ...teardown...\n  rmSync(this.canaryDir, { recursive: true, force: true })\n}","handlingStrategy":"validation","validationCode":"const { existsSync } = require('node:fs')\nif (watcherCanaryDir && existsSync(watcherCanaryDir)) {\n  console.error(`Watcher supervisor leaked canary dir: ${watcherCanaryDir}`)\n  // optional backstop: rmSync(watcherCanaryDir, { recursive: true, force: true })\n  process.exit(1)\n}","typeGuard":null,"tryCatchPattern":"try { /* harness body */ }\nfinally {\n  supervisor?.dispose()\n  if (watcherCanaryDir && existsSync(watcherCanaryDir)) {\n    rmSync(watcherCanaryDir, { recursive: true, force: true }) // backstop\n    throw new Error(`Watcher supervisor leaked its canary directory: ${watcherCanaryDir}`)\n  }\n}","preventionTips":["Make dispose() idempotently remove the canary dir it created, mirroring the creation path.","Record watcherCanaryDir from the same variable the supervisor uses to create it.","Add an rmSync backstop in the harness finally so a leak does not leave temp dirs on disk."],"tags":["file-watcher","resource-leak","temp-dir","cleanup","fault-injection"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}