stablyai/orca · error · Error

Watcher supervisor leaked its canary directory: ${watcherCan

Error message

Watcher supervisor leaked its canary directory: ${watcherCanaryDir}

What it means

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.

Source

Thrown at config/scripts/runtime-file-watcher-fault-harness.mjs:176

        postCrashEventDelivered: true
      })
    )
  } finally {
    try {
      await subscription?.unsubscribe()
    } finally {
      supervisor?.dispose()
      await Promise.all([
        createdRootPath ? rm(createdRootPath, { recursive: true, force: true }) : Promise.resolve(),
        rootPath && rootPath !== createdRootPath
          ? rm(rootPath, { recursive: true, force: true })
          : Promise.resolve(),
        bundleDir ? rm(bundleDir, { recursive: true, force: true }) : Promise.resolve()
      ])
    }
  }
  if (watcherCanaryDir && existsSync(watcherCanaryDir)) {
    throw new Error(`Watcher supervisor leaked its canary directory: ${watcherCanaryDir}`)
  }
}

await main()

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Ensure WatcherProcessSupervisor.dispose() removes the canary dir it created (mirror the creation path exactly).
  2. Verify the harness records watcherCanaryDir from the same variable the supervisor uses to create it.
  3. 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.
  4. 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.

Example fix

// before
// supervisor.dispose() does not remove canary dir
// after (in WatcherProcessSupervisor)
dispose() {
  // ...teardown...
  rmSync(this.canaryDir, { recursive: true, force: true })
}
Defensive patterns

Strategy: validation

Validate before calling

const { existsSync } = require('node:fs')
if (watcherCanaryDir && existsSync(watcherCanaryDir)) {
  console.error(`Watcher supervisor leaked canary dir: ${watcherCanaryDir}`)
  // optional backstop: rmSync(watcherCanaryDir, { recursive: true, force: true })
  process.exit(1)
}

Try / catch

try { /* harness body */ }
finally {
  supervisor?.dispose()
  if (watcherCanaryDir && existsSync(watcherCanaryDir)) {
    rmSync(watcherCanaryDir, { recursive: true, force: true }) // backstop
    throw new Error(`Watcher supervisor leaked its canary directory: ${watcherCanaryDir}`)
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/9f3c3c146dd17878. Report an issue: GitHub.