Skip to content

BAD CODE™: v19 runtime cache leaks persistent Git ref sessions #101

Description

@flyingrobots

Problem

Think's native v19 adapter caches each opened git-warp Runtime in a module-global sessions Map. A session is released only when a caller explicitly invokes closeNativeMemory or clearWarpRuntimeCache, but the product/read handles have no owned close method and most callers and port fixtures never invoke either function.

This was latent with git-warp 19.0.2. After installing git-warp 19.1.0, whose fast path keeps bounded Git sessions resident, the missing lifecycle ownership becomes observable as leaked git update-ref --stdin subprocesses and a test process that never exits.

The defect belongs at the Think integration boundary: git-warp Runtime exposes close and Symbol.asyncDispose, and NativeMemorySession.close delegates to runtime.close. Think opens and caches the runtime but does not make closure structurally unavoidable.

Evidence

Environment:

  • Think runtime checkout: /Users/james/git/think-v19-runtime
  • Think commit: 3f7219a
  • git-warp: 19.1.0
  • git-cas resolved by git-warp: 6.5.10
  • Plumbing resolved by git-warp: 3.3.0
  • Node: 26.0.0

Reproduction:

  1. Point the existing Think v19 runtime at the public global git-warp 19.1.0 package.

  2. Run npm run test:fast.

  3. Lint, runtime-truth, and hexagonal-boundary ratchets pass.

  4. Early real-Git Browse/History assertions pass.

  5. The port runner stops producing TAP output and remains resident for more than three minutes.

  6. Process inspection shows ten child processes such as:

    /Applications/Xcode.app/Contents/Developer/usr/bin/git update-ref --stdin

    Each child is sleeping with stdin held open by one of the test workers.

  7. Interrupting only the launched test command terminates every leaked child; no mind or repository ref was changed.

Relevant code at the witnessed commit:

  • src/store/native-runtime.js keeps const sessions = new Map().
  • openNativeMemory inserts NativeMemorySession instances into that map.
  • closeNativeMemory is the only map eviction and Runtime.close path.
  • src/store/runtime.js exposes clearWarpRuntimeCache, but no production or fixture caller owns it.
  • openProductReadHandle returns a frozen read handle with no close or async-dispose method.

Operational contrast:

  • codex-think --remember --json completes because the wrapper process exits; after 19.1.0 it dropped from 93.0 seconds to 4.21 seconds for the same 26 matches.
  • claude-think --remember --json dropped from 19.1 seconds to 1.63 seconds for the same 50 matches.

The release is delivering its intended speedup, but process exit is not a valid resource-management strategy for MCP servers, long-lived applications, benchmarks, or test workers.

Required outcome

  • Give every Think-owned runtime/read handle an explicit idempotent close and Symbol.asyncDispose path.
  • Make composition roots and test fixtures own that handle with try/finally or await using.
  • Define whether the module cache is reference-counted, lease-based, or process-scoped; do not leave ownership ambient.
  • Ensure reopening and repair replace sessions without orphaning the previous Runtime.
  • Add a regression test that opens, reads/writes, closes, and proves no git update-ref --stdin, cat-file, or other persistent Git child remains.
  • Add a process-exit test whose success cannot be faked by a test-runner timeout or forced teardown.
  • Run the complete port and acceptance gates against public git-warp 19.1.0.
  • Preserve the measured recall speedup and do not disable persistent lower-layer sessions merely to hide the consumer leak.

Non-goals

  • Do not change the Think memory data model, adopt EntityOccurrence, or start the contextual-mind migration.
  • Do not weaken the port suite or add forced process.exit calls.
  • Do not revert git-warp, git-cas, or Plumbing performance work as a substitute for lifecycle ownership.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingjavascriptPull requests that update javascript code

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions