Skip to content

PostToolUse hook throws NotCapable when LD_LIBRARY_PATH is set (nix-shell) #96

Description

@systemfsoftware-maker

Problem Statement

In a nix-shell / direnv project the hook never runs. LD_LIBRARY_PATH is set by the shell (e.g. /nix/store/...gcc-15.3.0-lib/lib), and the Deno hook host blocks any Deno.Command that inherits it unless the variable is allowlisted. The user sees NotCapable: Requires --allow-run permissions to spawn subprocess with LD_LIBRARY_PATH and the fallback prints "comment-checker did not run, so nothing checked this write" — no file is checked at all. The binary itself does not need LD_LIBRARY_PATH and runs hermetically inside bwrap.

Goal

The PostToolUse hook executes comment-checker --strip (or the direnv exec fallback) and returns its exit code when LD_LIBRARY_PATH is present in the environment, for all nix-shell and non-nix-shell invocations.

Evidence: Current Behavior

hooks/run.ts:1 — allowlist omits LD_LIBRARY_PATH:

#!/usr/bin/env -S deno run --allow-read --allow-run=comment-checker,direnv --allow-env=CLAUDE_PROJECT_DIR,PATH,HOME

hooks/run.ts:24-37 — spawn inherits ambient env and throws before the binary runs:

async function run(cmd: string, args: string[]): Promise<number | undefined> {
  try {
    const { code } = await new Deno.Command(cmd, {
      args,
      stdin: 'inherit',
      stdout: 'inherit',
      stderr: 'inherit',
    }).output()

Runtime error observed at write time (Deno 2.9.4, LD_LIBRARY_PATH=/nix/store/r48746qznwqxxl9qzd8f08ny8mg1dg2y-gcc-15.3.0-lib/lib):

error: Uncaught (in promise) NotCapable: Requires --allow-run permissions to spawn subprocess with LD_LIBRARY_PATH environment variable.

Orientation

Affected: hooks/run.ts and flake.nix / wrapper bin/comment-checker (bwrap sandbox). The inner binary at /nix/store/...comment-checker-0.3.4/bin/comment-checker is an ELF with RPATH to its nix glibc and needs no LD_LIBRARY_PATH (ldd resolves libc.so.6 and libgcc_s.so.1 from /nix/store directly). Match existing hook pattern (try comment-checker on PATH, then direnv exec).

Non-Counting Outcomes

  • Adding LD_LIBRARY_PATH to --allow-env without verifying the hook actually succeeds when the variable is set (allowlist change alone, no spawn fix).
  • Stripping LD_LIBRARY_PATH only on the direnv fallback but not on the direct comment-checker path.
  • Suppressing the NotCapable with a catch that returns success — hook goes green while nothing is checked.
  • Documenting "unset LD_LIBRARY_PATH before running" instead of fixing the hook.
  • Verifying only outside nix-shell (no LD_LIBRARY_PATH) and claiming the fix works.

Acceptance Criteria

  • With LD_LIBRARY_PATH=/nix/store/r48746qznwqxxl9qzd8f08ny8mg1dg2y-gcc-15.3.0-lib/lib in the environment, hooks/run.ts spawns comment-checker --strip without NotCapable and returns its exit code (gatekeeper: LD_LIBRARY_PATH=/nix/store/r48746qznwqxxl9qzd8f08ny8mg1dg2y-gcc-15.3.0-lib/lib deno run --allow-read --allow-run=comment-checker,direnv --allow-env=CLAUDE_PROJECT_DIR,PATH,HOME,LD_LIBRARY_PATH hooks/run.ts or equivalent with the fix — verifier runs hook with the variable set and checks exit code).
  • Direct and direnv exec paths both handle LD_LIBRARY_PATH (unset or allowlisted) — no path still throws NotCapable.
  • Outside nix-shell (no LD_LIBRARY_PATH) behavior is unchanged — hook still finds comment-checker on PATH or via direnv exec.
  • deno task check (or deno lint/deno check as defined in repo) exits 0 after the change — no allowlist regression.

Boundaries

  • Never: require the user to unset LD_LIBRARY_PATH manually.
  • Always: preserve bwrap sandboxing of the inner binary (wrapper at bin/comment-checker).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions