---
slug: spt-env-vars-empty
status: root_cause_found
trigger: SPT live/owl skills fail because $LIVE and $OWL env vars expand to empty strings in this Claude Code session
created: 2026-04-15
updated: 2026-04-15
---

# Debug Session: spt-env-vars-empty

## Symptoms

- **Expected behavior:** `$LIVE` and `$OWL` env vars are populated by the spt plugin's SessionStart hook, so commands like `$LIVE start doyle` and `$OWL poll doyle listen --live` work out of the box per the `/spt:live` skill instructions.
- **Actual behavior:** Both env vars expand to empty strings. `$LIVE start doyle` resolves to ` start doyle`, bash prints `The system cannot find the file doyle` (Windows bash trying to exec "start doyle" as a program). `$LIVE psyche-download doyle` resolves to ` psyche-download doyle`, bash prints `psyche-download: command not found`.
- **Error messages:**
  - `/usr/bin/bash: line 1: psyche-download: command not found`
  - `The system cannot find the file doyle.` (from `start doyle` invocation)
  - `echo "LIVE=$LIVE"; echo "OWL=$OWL"` → `LIVE=\nOWL=` (both empty)
- **Timeline:** Observed immediately after `/clear` followed by `/spt:live doyle` in this session. Unknown whether this is a new regression or a persistent issue across sessions.
- **Reproduction:** In a fresh Claude Code session with the spt plugin installed (base dir: `C:\Users\decid\.ccs\instances\bigscreen\plugins\cache\cplugs\spt\1.7.1\skills\live`), run `echo "LIVE=$LIVE OWL=$OWL"` — both print empty.

## Current Focus

- hypothesis: CONFIRMED — The spt plugin cache dir has `.orphaned_at` marker. Claude Code's SessionStart hook runs and writes the session-env file correctly, but Claude Code does NOT source env files from orphaned plugin dirs into Bash tool subprocesses. Orphaning triggered by stale `gitCommitSha` in `installed_plugins.json`.
- next_action: apply fix — refresh plugin install metadata and remove orphan marker
- test: N/A (root cause found by direct evidence)
- expecting: N/A

## Evidence

- timestamp: 2026-04-15 — user confirmed `echo "LIVE=$LIVE"; echo "OWL=$OWL"` prints empty values for both in the Bash tool subprocess
- timestamp: 2026-04-15 — the `/spt:live doyle` command text itself references the plugin base directory `C:\Users\decid\.ccs\instances\bigscreen\plugins\cache\cplugs\spt\1.7.1\skills\live`, so the plugin is installed
- timestamp: 2026-04-15 — `/spt:live` skill doc says: "All commands use `$OWL` and `$LIVE` env vars, auto-injected by the plugin's SessionStart hook. If commands fail with 'command not found', restart the Claude Code session so the SessionStart hook re-runs."
- timestamp: 2026-04-15 — `~/.claude/settings.json` has `"spt@cplugs": true` in enabledPlugins; no hardcoded env.OWL/env.LIVE pinning. Settings.json is clean of stale env.
- timestamp: 2026-04-15 — Plugin hook manifest at `~/.claude/plugins/cache/cplugs/spt/1.7.1/hooks/hooks.json` registers `SessionStart → $CLAUDE_PLUGIN_ROOT/owl.exe plugin-session-start`. Binary code in `src/owl/plugin_session_start.rs` writes `export OWL="..."` + `export LIVE="..."` to `$CLAUDE_ENV_FILE`.
- timestamp: 2026-04-15 — Current session id is `af3bbd81-c4b5-422a-bf49-a2873e2330d0`. Session-env dir `~/.claude/session-env/af3bbd81-.../sessionstart-hook-2.sh` exists, was written at 21:05:01.484 (session started at 21:05:01.196). File contents are VALID:
  ```
  export OWL="C:/Users/decid/.ccs/instances/bigscreen/plugins/cache/cplugs/spt/1.7.1/owl.exe"
  export LIVE="C:/Users/decid/.ccs/instances/bigscreen/plugins/cache/cplugs/spt/1.7.1/owl.exe live"
  export OWL_SESSION_ID="af3bbd81-c4b5-422a-bf49-a2873e2330d0"
  ```
- timestamp: 2026-04-15 — Manually sourcing the session-env file makes `$OWL doctor` run successfully against the binary. Binary and file content are both healthy; only the automatic sourcing is failing.
- timestamp: 2026-04-15 — `.orphaned_at` marker exists at both `~/.claude/plugins/cache/cplugs/spt/1.7.1/.orphaned_at` and `~/.ccs/instances/bigscreen/plugins/cache/cplugs/spt/1.7.1/.orphaned_at` (same inode — cache is a symlink from the .ccs instance to the shared .claude/plugins/cache). Marker created at 21:05:01.284 — **200 ms BEFORE the env file was written at 21:05:01.484**. Claude Code orphaned the plugin during session startup, then ran the (already-queued) hook anyway, then ignored the output.
- timestamp: 2026-04-15 — Manual install at `~/.claude/plugins/spt/` (the pre-plugin-system layout per DEPLOY.md) has the same binary but is NOT orphaned. However, `~/.ccs/shared/plugins/installed_plugins.json` (which bigscreen instance uses via symlink) records the spt install path as `C:\Users\decid\.ccs\instances\bigscreen\plugins\cache\cplugs\spt\1.7.1` — the orphaned cache path, not the healthy manual install.
- timestamp: 2026-04-15 — `installed_plugins.json` for `spt@cplugs` has `"gitCommitSha": "e569409f720405a8336ef50e565a04071030f7c2"` — a commit from **2026-04-02** titled "spt: split messaging into individual skill directories". The cplugs marketplace git HEAD is `24e97a9bdd85e25ec0423c11dedc1eb1909d1ba7` titled "spt v1.7.1: remove env-setup skill + subcommand" from today (2026-04-15). **The SHA in installed_plugins.json never got bumped** despite the deploy in commit `f21b523` updating the marketplace and wiping/refreshing the cache. `"lastUpdated": "2026-04-16T01:37:58.252Z"` (= 18:37:58 PDT today) indicates metadata was touched but the SHA field was not. This SHA mismatch between marketplace HEAD and installed record is almost certainly what makes Claude Code mark the cache orphaned at session start.
- timestamp: 2026-04-15 — Shell snapshot (`snapshot-bash-1776312344369-4sm5of.sh`) contains no `export OWL` / `export LIVE` / `source .../sessionstart-hook-2.sh`. Injection of per-session env vars is a separate Claude Code mechanism layered on top of the shell snapshot — it reads `sessionstart-hook-N.sh` files and injects their exports into each Bash tool subprocess. For THIS session that injection is silently suppressed because the file came from an orphaned plugin.
- timestamp: 2026-04-15 — `screen-timelapse@cplugs` in the same `installed_plugins.json` has `gitCommitSha: c73a3392...` which DOES exist at marketplace HEAD. That plugin is not orphaned and works normally. Isolated difference: the SHA match/mismatch.
- timestamp: 2026-04-15 — Running `"$CLAUDE_PLUGIN_ROOT/owl.exe plugin-session-start" < /dev/null` with a temp `CLAUDE_ENV_FILE` produces a correct env file with exit 0. Binary logic is fully functional.

## Eliminated

- Hook not registered — eliminated: `plugin/spt/hooks/hooks.json` registers `SessionStart → $CLAUDE_PLUGIN_ROOT/owl.exe plugin-session-start` at all three install locations (cache/cplugs/spt/1.7.1, marketplaces/cplugs/plugins/spt, plugins/spt).
- Hook failing silently — eliminated: `sessionstart-hook-2.sh` contains correct output for this session.
- Subcommand bug — eliminated: manual invocation of `plugin-session-start` with temp env file produces correct output; binary is healthy.
- Stale env pin in `~/.claude/settings.json` — eliminated: settings.json has no env.OWL/env.LIVE keys.
- Windows path / BOM / line-ending corruption — eliminated: `od -c` shows clean ASCII + LF line endings, no BOM, no extra chars.
- /reload-plugins race (prior debug's conclusion) — eliminated by this session: session was freshly started, SessionStart fully fired, file was written, yet env vars still empty. Restart-based fix from prior debug's runbook DOES NOT RESOLVE THIS CASE.

## Resolution

- **root_cause:** Claude Code marked `~/.claude/plugins/cache/cplugs/spt/1.7.1/` as orphaned (`.orphaned_at` file created at session start, 200 ms before the hook ran) because `installed_plugins.json` for `spt@cplugs` holds a stale `gitCommitSha` (`e569409…`, from April 2) that no longer matches the cplugs marketplace HEAD (`24e97a9…`, the v1.7.1 commit from today). Claude Code still fires the SessionStart hook — binary writes a valid `sessionstart-hook-2.sh` pointing at the orphaned dir's `owl.exe` — but Claude Code refuses to inject env vars from an orphaned plugin's output into subsequent Bash tool subprocesses. Result: the session-env file is correct on disk, the binary works when invoked manually, but `$OWL`/`$LIVE` remain empty in every Bash tool call. The bug `/spt:env-setup` used to paper over (prior debug `plugin-env-vars-not-injected`) was this same mechanism, and removing `env-setup` in commit `f21b523` left users with no in-session escape hatch when Claude Code orphans the plugin.

  Two upstream factors make this likely to recur:
  1. Claude Code's plugin installer does not always update `gitCommitSha` when re-syncing a plugin cache (here `lastUpdated` WAS bumped to 2026-04-16T01:37:58 but `gitCommitSha` was left at the April-2 value — either a Claude Code bug or a side-effect of the manual `rm -rf cache + cp` deploy procedure bypassing the official `/plugin install` code path).
  2. The `plugin_session_start` version trampoline in `src/owl/plugin_session_start.rs` skips orphaned sibling dirs as upgrade targets, but does **not** detect or repair the case where the *current* exe is itself in an orphaned dir. When the active cache dir is orphaned and there is no newer sibling (here only 1.7.1 exists), the trampoline silently writes the orphaned path.

- **fix:** Two-layer fix.
  1. **Immediate (this install):** Remove the `.orphaned_at` marker AND update `installed_plugins.json`'s `gitCommitSha` for `spt@cplugs` to the current marketplace HEAD (`24e97a9bdd85e25ec0423c11dedc1eb1909d1ba7`). Or, cleaner, uninstall and reinstall the plugin via `/plugin uninstall spt@cplugs` then `/plugin install spt@cplugs` so Claude Code re-writes the metadata properly. Restart the Claude Code session so the un-orphaned plugin's hook output gets sourced.
  2. **Structural (code change, in this repo):** Teach the version trampoline in `src/owl/plugin_session_start.rs` to detect self-orphaned state (current exe dir has `.orphaned_at`) and, when no newer non-orphaned sibling exists, fall back to `~/.claude/plugins/spt/owl.exe` (the manual-install path) if that binary exists and is not orphaned. Additionally, the `DEPLOY.md` recipe that does `rm -rf ~/.claude/plugins/cache/cplugs/spt` followed by `cp -r` of a fresh cache should also run `/plugin install` (or otherwise update `installed_plugins.json`'s `gitCommitSha`) so future sessions don't orphan the freshly-deployed cache.

- **verification:** After applying fix 1, run `echo "OWL=$OWL LIVE=$LIVE"` in a new session and confirm both are populated. Run `$OWL doctor` and confirm PASS for "env vars". Fix 2 would be covered by a unit test that stubs `.orphaned_at` on the current exe's dir and asserts the trampoline falls back to `~/.claude/plugins/spt/owl.exe`.

- **files_changed:** (pending user decision on fix scope)

## Specialist Hint

`rust` — the structural fix (#2) is a small change in `src/owl/plugin_session_start.rs`. No specialist skill registered for pure Rust, so no auto-dispatch needed.
