fix(plugin): session-start hook reads API token from CLAUDE_PLUGIN_OPTION env, not user_config placeholder #64

Merged
bvandeusen merged 1 commits from dev into main 2026-06-10 00:52:02 -04:00
Owner

Problem

Phase 1 push-channel test failed on first real install: no standing-context block injected at SessionStart.

Root cause: the SessionStart hook passed the key via SCRIBE_TOKEN="${user_config.api_token}" in hooks.json, but api_token is sensitive: true. Claude Code keeps sensitive userConfig in the keychain and does not interpolate it into hook command strings (only into mcpServers headers — which is why MCP auth worked). The hook received the literal string ${user_config.api_token}, which is non-empty so it passed the guard and was sent as the Bearer token → /api/plugin/context correctly returns 401 → curl -f fails → fail-open → nothing injected.

The endpoint itself is healthy: deployed, login_required accepts Bearer fmcp_ keys, and build_session_context returns {"context": ...}.

Fix

  • hooks.json — command is now just bash "${CLAUDE_PLUGIN_ROOT}/hooks/scribe_session_context.sh" (dropped the broken ${user_config.*} assignments, which would also shadow the real env var).
  • scribe_session_context.sh — reads the harness-exported CLAUDE_PLUGIN_OPTION_api_endpoint/api_token/project_id env vars (SCRIBE_* still override for the settings.json dogfooding path); added a guard that treats any unexpanded ${...} literal as unset so the hook fails open cleanly instead of 401-ing.
  • plugin.json — bump 0.1.10.1.2 so installs refresh the cache.

Verification

  • bash -n clean.
  • Bogus-token and unexpanded-placeholder cases both fail open (exit 0, no output).
  • Endpoint probes confirm /api/plugin/context returns 401 only on missing/invalid keys.
  • End-to-end (block appears in a fresh session) to be confirmed after merge + plugin update.

Tracked on Scribe task 755 (Scribe-as-plugin, Phase 1).

🤖 Generated with Claude Code

## Problem Phase 1 push-channel test failed on first real install: no standing-context block injected at SessionStart. **Root cause:** the SessionStart hook passed the key via `SCRIBE_TOKEN="${user_config.api_token}"` in `hooks.json`, but `api_token` is `sensitive: true`. Claude Code keeps sensitive userConfig in the keychain and **does not interpolate it into hook command strings** (only into `mcpServers` headers — which is why MCP auth worked). The hook received the literal string `${user_config.api_token}`, which is non-empty so it passed the guard and was sent as the Bearer token → `/api/plugin/context` correctly returns 401 → `curl -f` fails → fail-open → nothing injected. The endpoint itself is healthy: deployed, `login_required` accepts `Bearer fmcp_` keys, and `build_session_context` returns `{"context": ...}`. ## Fix - `hooks.json` — command is now just `bash "${CLAUDE_PLUGIN_ROOT}/hooks/scribe_session_context.sh"` (dropped the broken `${user_config.*}` assignments, which would also shadow the real env var). - `scribe_session_context.sh` — reads the harness-exported `CLAUDE_PLUGIN_OPTION_api_endpoint/api_token/project_id` env vars (`SCRIBE_*` still override for the settings.json dogfooding path); added a guard that treats any unexpanded `${...}` literal as unset so the hook fails open cleanly instead of 401-ing. - `plugin.json` — bump `0.1.1` → `0.1.2` so installs refresh the cache. ## Verification - `bash -n` clean. - Bogus-token and unexpanded-placeholder cases both fail open (exit 0, no output). - Endpoint probes confirm `/api/plugin/context` returns 401 only on missing/invalid keys. - End-to-end (block appears in a fresh session) to be confirmed after merge + plugin update. Tracked on Scribe task 755 (Scribe-as-plugin, Phase 1). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 1 commit 2026-06-10 00:47:59 -04:00
The SessionStart push-channel hook passed the key via
SCRIBE_TOKEN="${user_config.api_token}" in hooks.json, but api_token is
sensitive:true. Claude Code keeps sensitive userConfig in the keychain and
does not interpolate it into hook command strings (only into mcpServers
headers), so the hook received the literal placeholder, sent it as the Bearer
token, and the context endpoint 401'd -> fail-open -> no context injected.

Read the harness-exported CLAUDE_PLUGIN_OPTION_<key> env vars instead (SCRIBE_*
still override for the settings.json dogfooding path), and treat any unexpanded
${...} literal as unset so the hook fails open cleanly instead of 401-ing.
Bump 0.1.1 -> 0.1.2 so installs refresh the cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bvandeusen merged commit 6cc47c7222 into main 2026-06-10 00:52:02 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledScribe#64