# RFC 0051: Portable Hook Binary Resolution

**Status**: Draft
**Created**: 2026-02-01
**Author**: Claude Opus 4.5
**Related**: RFC 0038, RFC 0049

## Problem Statement

Claude Code PreToolUse hooks run in a minimal environment without full PATH initialization. When hooks invoke commands by name (e.g., `blue guard`), the shell cannot resolve the binary and hangs indefinitely.

### Current Workaround

The guard hook uses a hardcoded absolute path:
```bash
/Users/ericg/letemcook/blue/target/release/blue guard --path="$FILE_PATH"
```

This works but is:
- **Not portable**: Different paths on different machines
- **Fragile**: Breaks if binary moves
- **Not team-friendly**: Each developer needs different paths

### Root Cause

Hook processes don't inherit the user's shell initialization (`.bashrc`, `.zshrc`). This means:
- Custom PATH entries (like `~/.cargo/bin`) are not available
- Homebrew paths may be missing
- Language version managers (nvm, rbenv) don't work

This is likely intentional for security (preventing secrets/aliases in hooks).

## Proposed Solution

Use `$CLAUDE_PROJECT_DIR` environment variable for portable binary resolution.

### Option A: Project-Relative Binary (Recommended)

Update hook to use `$CLAUDE_PROJECT_DIR`:

```bash
#!/bin/bash
# .claude/hooks/guard-write.sh

FILE_PATH=$(jq -r '.tool_input.file_path // empty')

if [ -z "$FILE_PATH" ]; then
    exit 0
fi

# Use CLAUDE_PROJECT_DIR for portable path resolution
"$CLAUDE_PROJECT_DIR/target/release/blue" guard --path="$FILE_PATH"
```

**Pros:**
- Portable across team members
- Works with any checkout location
- Documented Claude Code pattern

**Cons:**
- Requires binary in project directory
- Must rebuild after checkout

### Option B: SessionStart PATH Injection

Add a SessionStart hook that adds blue to PATH:

```json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [{
          "type": "command",
          "command": ".claude/hooks/setup-path.sh"
        }]
      }
    ]
  }
}
```

```bash
#!/bin/bash
# .claude/hooks/setup-path.sh
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo "export PATH=\"$CLAUDE_PROJECT_DIR/target/release:\$PATH\"" >> "$CLAUDE_ENV_FILE"
fi
exit 0
```

**Pros:**
- `blue` works by name in all subsequent hooks
- Cleaner hook scripts

**Cons:**
- Requires SessionStart hook
- More complex setup
- Session-specific (resets on restart)

### Option C: Installed Binary with Explicit PATH

For teams that install blue globally:

```bash
#!/bin/bash
# Explicitly set PATH to include common install locations
export PATH="$HOME/.cargo/bin:/usr/local/bin:$PATH"

FILE_PATH=$(jq -r '.tool_input.file_path // empty')
if [ -z "$FILE_PATH" ]; then
    exit 0
fi

blue guard --path="$FILE_PATH"
```

**Pros:**
- Works with installed binaries
- Standard Unix pattern

**Cons:**
- Must enumerate all possible install locations
- Still not fully portable

## Recommendation

**Option A** (project-relative binary) is recommended because:
1. It's documented by Claude Code
2. It's portable across machines
3. It doesn't require additional hooks
4. It works with the existing build workflow

## Implementation Plan

- [ ] Update `.claude/hooks/guard-write.sh` to use `$CLAUDE_PROJECT_DIR`
- [ ] Update `.claude/settings.json` to use quoted project dir path
- [ ] Test on fresh checkout
- [ ] Document in README or CONTRIBUTING.md

## Migration

Before:
```bash
/Users/ericg/letemcook/blue/target/release/blue guard --path="$FILE_PATH"
```

After:
```bash
"$CLAUDE_PROJECT_DIR/target/release/blue" guard --path="$FILE_PATH"
```

## Testing

1. Clone repo to new location
2. Build: `cargo build --release -p blue`
3. Run Claude Code
4. Attempt a write operation
5. Verify guard runs without hanging

## References

- Claude Code Hooks Documentation: https://code.claude.com/docs/en/hooks.md
- RFC 0038: SDLC Workflow Discipline
- RFC 0049: Synchronous Guard Command
