RCA-2026-04-03-001: bluetoothctl PATH Resolution Failure in Claude Code Shell
Executive Summary
User needed to connect Samsung Galaxy Buds3 Pro urgently before a call. The bluetoothctl binary existed at /usr/bin/bluetoothctl but command -v bluetoothctl returned exit code 127 (command not found). Root cause: the Claude Code shell environment initializes with a malformed $PATH that contains a literal $PATH string instead of the expanded value, preventing resolution of standard /usr/bin/ binaries. Resolution: invoked bluetoothctl via absolute path /usr/bin/bluetoothctl. This RCA documents the shell environment divergence and establishes defensive patterns.
Timeline
| Time | Event |
|---|---|
2026-04-03 ~AM |
User requested urgent Bluetooth connection for incoming call |
+10s |
|
+20s |
|
+30s |
|
+40s |
|
+50s |
|
+60s |
User on call, requested RCA for learning |
Problem Statement
Symptoms
-
bluetoothctlreturned exit code 127 (command not found) -
which bluetoothctlalso failed -
Binary confirmed present at
/usr/bin/bluetoothctl
Expected Behavior
Standard binaries in /usr/bin/ should be resolvable by name in any shell session.
Actual Behavior
The Claude Code subshell $PATH contained a literal $PATH string instead of expanded system paths:
$ echo $PATH
/home/evanusmodestus/.local/bin:/home/evanusmodestus/.cargo/bin:/home/evanusmodestus/.local/bin:$PATH
The trailing $PATH was never expanded, so /usr/bin, /usr/sbin, /bin, /sbin were missing from the search path entirely.
Root Cause
5 Whys Analysis
| Why # | Question and Answer |
|---|---|
1 |
Why wasn’t |
2 |
Why wasn’t |
3 |
Why was |
4 |
Why does this only affect Claude Code? |
5 |
Why wasn’t this caught before? |
Root Cause Statement
|
The Claude Code Bash tool shell environment has a malformed |
Contributing Factors
| Factor | Description | Preventable? |
|---|---|---|
Shell init order |
Claude Code may not source |
Partially (investigate init files) |
PATH construction |
A line like |
Yes (use absolute paths in exports) |
Rare Bash tool use |
Most work uses dedicated tools; PATH issues go unnoticed |
No (by design) |
Time pressure |
Incoming call compressed troubleshooting time |
No |
Impact
Severity
| Metric | Value |
|---|---|
Severity |
P3 (minor — workaround available) |
Duration |
~50 seconds to resolution |
Users/Systems Affected |
1 user, Claude Code shell environment |
Data Loss |
None |
Broader Implications
This affects any Bash tool invocation in Claude Code that relies on standard system binaries by name:
-
systemctl,journalctl,ip,ss,lsblk -
pacman,paru -
docker,podman -
ssh,scp,rsync
Any of these would also fail unless invoked with absolute path.
Resolution
Immediate Action (Workaround)
Used absolute path to invoke the binary:
/usr/bin/bluetoothctl devices
/usr/bin/bluetoothctl connect 24:24:B7:B5:1C:CC
Verification
# Confirmed connection
/usr/bin/bluetoothctl info 24:24:B7:B5:1C:CC | awk '/Name|Connected/'
Shell Environment Deep Dive
How PATH Should Work
1. /etc/zsh/zshenv ← System-wide, always sourced first
2. ~/.zshenv ← User, always sourced
3. /etc/zsh/zprofile ← System-wide, login shells only
4. ~/.zprofile ← User, login shells only
5. /etc/zsh/zshrc ← System-wide, interactive shells only
6. ~/.zshrc ← User, interactive shells only
Standard /usr/bin comes from step 1 (/etc/zsh/zshenv) or is inherited from the parent process.
Debugging Commands
# Show raw PATH
echo $PATH
# Show PATH one-per-line (readable)
echo $PATH | tr ':' '\n'
# Check if /usr/bin is in PATH
echo $PATH | tr ':' '\n' | grep -x '/usr/bin'
# Find where PATH is set in zsh init files
grep -n 'PATH' ~/.zshenv ~/.zshrc ~/.zprofile 2>/dev/null
# Compare Claude Code shell vs terminal
# In terminal:
echo $PATH | tr ':' '\n' | wc -l
# Check what shell Claude Code uses
echo $SHELL
ps -p $$ -o comm=
The Fix Pattern
When constructing PATH in shell init files, ensure system paths are always present:
# DEFENSIVE: Always include system paths explicitly
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
# OR: Only prepend if PATH already has content
if [[ -n "$PATH" ]]; then
export PATH="$HOME/.local/bin:$PATH"
else
export PATH="$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin"
fi
Defensive Patterns for Claude Code
When Bash Tool Fails with Exit 127
# 1. Check if binary exists
ls /usr/bin/<command> /usr/local/bin/<command> 2>/dev/null
# 2. Use absolute path
/usr/bin/<command> <args>
# 3. Or fix PATH inline
PATH="/usr/bin:/usr/sbin:$PATH" <command> <args>
Common Absolute Paths (Arch Linux)
| Command | Path |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Preventive Measures
Short-term (This week)
| Action | Owner | Status |
|---|---|---|
Investigate |
Evan |
[ ] Pending |
Add defensive PATH construction to shell init |
Evan |
[ ] Pending |
Verify |
Evan |
[ ] Pending |
Long-term
| Action | Owner | Status |
|---|---|---|
Add Claude Code shell environment validation to dotfiles |
Evan |
[ ] Pending |
Document absolute paths for frequently used binaries in codex |
Evan |
[ ] Pending |
Lessons Learned
What went well
-
Quickly identified the binary existed via
ls /usr/bin/bluetooth* -
Absolute path workaround resolved issue in seconds
-
Connected before the call started
What could be improved
-
Should have a pre-validated shell environment for Claude Code
-
PATH issues are silent until they bite — need a health check
Key Takeaways
|
Connection to RCA-2026-03-27-001
RCA-2026-03-27-001 addressed the knowledge gap (not knowing bluetoothctl commands). This RCA addresses the environment gap (knowing the command but the shell can’t find it). Together they cover both failure modes for Bluetooth CLI operations.
Metadata
| Field | Value |
|---|---|
RCA ID |
RCA-2026-04-03-001 |
Author |
Evan Rosado |
Date Created |
2026-04-03 |
Last Updated |
2026-04-03 |
Status |
Final |
Review Date |
2026-05-03 |