diff --git a/AGENTS.md b/AGENTS.md index bc305cb1..0febb660 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -190,6 +190,7 @@ When the user's message matches a phrase below, read and follow the correspondin | "last screenshot", "see screenshot", "recent screenshot", "show screenshot", "last N screenshots" | `agents/skills/screenshot/SKILL.md` | | "investigate CI failures", "find flaky tests", "why is CI failing", "diagnose test flakiness", "flaky CI" | `agents/skills/ci-investigate/SKILL.md` | | "create skill", "new skill", "write skill", "add a slash command", "improve skill" | `agents/skills/write-skill/SKILL.md` | +| "add to PATH", "Bash tool PATH", "command not found in Claude Code", "works in terminal but not Claude Code", "CLAUDE_ENV_FILE", "env.PATH not working" | `agents/skills/bash-tool-path/SKILL.md` | | "prioritize", "RICE score", "backlog grooming", "sprint planning", "rank items" | `agents/skills/prioritize/SKILL.md` | | "swarm", "parallel agents", "agent team in conductor", "multi-agent", "spawn agents" | `agents/skills/swarm/SKILL.md` | | "orchestrate", "fable workflow", "dynamic workflow", "tiered workflow", "model-tiered build", "plan on fable implement with sonnet and opus" | `agents/skills/orchestrate/SKILL.md` | diff --git a/README.md b/README.md index f2245538..8d19d309 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ dots docker stop-all # Stop all Docker containers ## Agent Skills -Dots includes 68 reusable slash-command skills for AI coding agents, following the [Agent Skills](https://agentskills.io) open standard. Each skill lives in `agents/skills//SKILL.md` and is available as `/` in Claude Code after running `dots install agents`. +Dots includes 71 reusable slash-command skills for AI coding agents, following the [Agent Skills](https://agentskills.io) open standard. Each skill lives in `agents/skills//SKILL.md` and is available as `/` in Claude Code after running `dots install agents`. | Skill | Description | |-------|-------------| @@ -97,6 +97,7 @@ Dots includes 68 reusable slash-command skills for AI coding agents, following t | `/plan` | Generate an implementation plan from a PRD, spike, or spec | | `/contest` | Competing implementations with judge evaluation | | `/write-skill` | Create or improve a skill with best practices | +| `/bash-tool-path` | Safely add a directory to Claude Code's Bash tool PATH via a SessionStart hook and `CLAUDE_ENV_FILE` | | `/screenshot` | View recent screenshots from `~/Downloads` | | `/handoff` | Generate handoff prompt for another agent thread | | `/standup` | Daily standup summary from git activity | @@ -192,7 +193,7 @@ Run `dots install agents` to symlink them to `~/.claude/agents/`. ├── pi/ # pi.dev coding agent config (models.json only) │ ├── agents/ # Agent configuration -│ ├── skills/ # 68 reusable skills (SKILL.md per skill) +│ ├── skills/ # 71 reusable skills (SKILL.md per skill) │ └── custom/ # 3 custom agent types (.md per agent) │ └── tests/ # Skill test suite │ diff --git a/agents/skills/bash-tool-path/SKILL.md b/agents/skills/bash-tool-path/SKILL.md new file mode 100644 index 00000000..31432511 --- /dev/null +++ b/agents/skills/bash-tool-path/SKILL.md @@ -0,0 +1,92 @@ +--- +name: bash-tool-path +description: Safely add a directory to Claude Code's Bash tool PATH. Use when a binary runs in your terminal but is "command not found" inside Claude Code's Bash tool, when adding a directory to PATH for Claude Code, when configuring CLAUDE_ENV_FILE, or when settings.json env.PATH did not work. Explains why env.PATH replaces rather than appends PATH and is not variable-expanded, why ~/.zshrc PATH exports are dropped, and the supported SessionStart hook fix. +--- + +## Context + +- Worked example hook: ~/.dots/agents/hooks/session-start-path.sh +- Existing SessionStart hooks (user): !`grep -n SessionStart ~/.claude/settings.json 2>/dev/null | head -10` +- env.PATH in user settings (a footgun if present): !`grep -n '"PATH"' ~/.claude/settings.json 2>/dev/null | head -5` + +## When to use this skill + +Use this when a command that works in an interactive terminal fails with +"command not found" inside Claude Code's Bash tool, or when the user wants to +add a directory (a go/bin, cargo, asdf shim, or custom tool dir) to the PATH +that Claude Code's Bash tool sees. + +## Why the obvious fixes do not work + +Three footguns make this harder than it looks. State them plainly so nobody +re-discovers them by hand. + +1. settings.json `env` values are set **literally**. Claude Code does not + expand `$VAR` or `${VAR}` in them. So `"PATH": "${PATH}:/x/bin"` sets PATH + to that literal, broken string, not to the inherited PATH plus a directory. + +2. `env.PATH` **replaces** the inherited PATH wholesale. It cannot append, + and replacing it drops the directories Claude Code already needs (for + example plugin bin dirs). So `env.PATH` is the wrong tool for adding a dir. + +3. The Bash tool sources `~/.zshrc` (or `~/.bashrc`/`~/.profile`) at session + start **only to capture aliases, functions, and shell options**. PATH + `export` lines in `~/.zshrc` or `~/.zshenv` are not captured. Claude Code + builds the Bash tool PATH from the process that launched it (often a daemon), + not from your interactive shell, so daemon-launched sessions can lack + go/bin and similar dirs even though they resolve fine in your terminal. + +## The supported fix: a SessionStart hook that writes to CLAUDE_ENV_FILE + +Claude Code sources the file named by `$CLAUDE_ENV_FILE` before every Bash +command. Because the file is sourced, `$PATH` expands at source time, so you +can prepend a directory and preserve the inherited PATH. Populate that file +from a `SessionStart` hook. `$CLAUDE_ENV_FILE` is available to the +SessionStart, Setup, CwdChanged, and FileChanged hooks. + +A static alternative exists — point `CLAUDE_ENV_FILE` at a fixed shell script +before launching Claude Code — but the SessionStart hook is the portable, +dynamic choice and is what this repo ships. + +### Steps + +1. Write (or extend) a SessionStart hook script that appends an `export` line + per directory to `$CLAUDE_ENV_FILE`. Read the worked, tested reference + first: `~/.dots/agents/hooks/session-start-path.sh`. It is idempotent, + guards that each directory exists, and prepends rather than clobbers. Mirror + its shape rather than writing a new pattern from scratch. The core line is: + + ```bash + export PATH="/your/dir:$PATH" + ``` + + Keep `$PATH` literal (escaped or single-quoted in the heredoc/printf) so it + expands when the env file is sourced, not when the hook runs. + +2. Make the hook a no-op when `$CLAUDE_ENV_FILE` is unset, and append each + export only if the file does not already contain it (the file persists + across repeated SessionStart fires within a session). + +3. Register the hook in settings.json under `hooks.SessionStart`, invoked via + `bash`. In this repo, registration is handled by `dots install agents`; for + a standalone setup, add the SessionStart entry to `~/.claude/settings.json`. + Use the `/update-config` skill for the settings.json edit mechanics. + +4. Verify in a fresh session: run the bare command name in the Bash tool and + confirm it resolves. + +### What NOT to do + +- Do not set `env.PATH` in settings.json to add a directory (it replaces, and + is not variable-expanded — see footguns 1 and 2). +- Do not add the PATH export only to `~/.zshrc`/`~/.zshenv` and expect the + Bash tool to pick it up (footgun 3). + +## References + +- Bash tool behavior (what gets sourced at session start): + https://code.claude.com/docs/en/tools +- Persisting environment variables across Bash commands via CLAUDE_ENV_FILE + and SessionStart hooks: https://code.claude.com/docs/en/hooks +- Worked reference implementation in this repo: + ~/.dots/agents/hooks/session-start-path.sh diff --git a/agents/skills/write-skill/SKILL.md b/agents/skills/write-skill/SKILL.md index 2b242edf..c93e4aac 100644 --- a/agents/skills/write-skill/SKILL.md +++ b/agents/skills/write-skill/SKILL.md @@ -204,6 +204,7 @@ After writing, run two validation passes. Fix issues silently — only flag if t #### Syntax and Safety +- [ ] Dynamic context uses the LIVE exclamation-mark-plus-backtick syntax, NOT the `!{...}` curly-brace form. The curly form is a documentation-only placeholder used in this file's examples to prevent self-execution (see the note under Dynamic Context) — a real skill that uses it renders the line as inert text instead of running the command. - [ ] No `$()` in any dynamic context line - [ ] No `||` or `&&` operators in any dynamic context line - [ ] Error-prone commands use `2>/dev/null | head -N` (suppresses stderr AND neutralizes exit code)