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).

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions