Skip to content

test(storage): the suite leaks 159 control directories into the real user cache per run #3590

Description

@Joob1n

Problem

A clean run of the @maka/storage test suite leaves 159 directories behind in the developer's real cache directory. Nothing fails, so nothing surfaces it.

$ ls ~/Library/Caches/Maka/runtime-hosts | wc -l
27740
$ node --test --test-concurrency=2 "packages/storage/dist/__tests__/*.test.js"
ℹ tests 912
ℹ pass 898
ℹ fail 0
$ ls ~/Library/Caches/Maka/runtime-hosts | wc -l
27899

On this machine the directory has accumulated 27,740 entries. Disk cost is negligible (264K — most hold a single empty owner.lock), but the count grows without bound and nothing ever reclaims it.

Cause

resolveRootControlNamespace() deliberately resolves to the real OS account home and cannot be redirected by an environment variable:

// packages/storage/src/root-authority.ts
export function resolveRootControlNamespace(): string {
  const accountHome = userInfo().homedir;
  if (process.platform === 'darwin') {
    return join(accountHome, 'Library', 'Caches', 'Maka', 'runtime-hosts');
  }
  ...
}

That is reasonable for a trust boundary — owner.lock decides who may write a State Root, so an env var must not be able to move it. The consequence is that tests write there too, and the control directory sits outside the temporary State Root they create.

So a helper of this shape cleans up the State Root but not its control directory:

async function withTempDir(run: (base: string) => Promise<void>): Promise<void> {
  const base = await mkdtemp(join(tmpdir(), 'maka-runtime-policy-'));
  try {
    await run(base);
  } finally {
    await rm(base, { recursive: true, force: true });   // the State Root, not the control directory
  }
}

Tests that do clean up carry an extra explicit line:

await rm(join(resolveRootControlNamespace(), capability.rootId), { recursive: true, force: true });

There is no shared helper for it in @maka/storage, so the line is copied per file, and seventeen files do not have it.

Which files leak

Measured by running each test file on its own and counting the directory before and after:

Directories leaked File (packages/storage/src/__tests__/)
54 runtime-policy-stores.test.ts
22 managed-workspace-baseline.test.ts
18 managed-workspace-owner.test.ts
16 memory-bundle-store.test.ts
15 usage-stores.test.ts
5 artifact-writer-lock.test.ts
4 storage-writer-composition.test.ts
3 artifact-stores.test.ts
3 daily-review-authority.test.ts
3 goal-authority.test.ts
3 shell-run-authority.test.ts
3 sqlite-long-term-memory-store.test.ts
3 sqlite-workflow-store.test.ts
3 task-ledger-authority.test.ts
2 state-root-composition.test.ts
1 project-catalog-authority.test.ts
1 sqlite-core-execution-store.test.ts

Total 159, matching the whole-suite delta.

@maka/runtime-host was checked the same way and does not leak: execution-host-recovery.test.ts and storage/root-authority.test.ts both measured a delta of 0, because their shared ExecutionFixture.close() already removes the control directory, the endpoint directories and the base.

Proposed fix

Add one shared helper under packages/storage/src/__tests__/fixtures/, mirroring runtime-host's endpoint-hygiene.ts, and call it from the seventeen files where a temporary State Root is torn down. This keeps the trust boundary exactly as it is and only makes the tests clean up what they created.

Out of scope here

Reclaiming control directories that are already stale is a separate question. It needs a definition of "stale" that is safe against a live owner.lock, so it should not ride along with a test-hygiene change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions