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).
## 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)
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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}"inhooks.json, butapi_tokenissensitive: true. Claude Code keeps sensitive userConfig in the keychain and does not interpolate it into hook command strings (only intomcpServersheaders — 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/contextcorrectly returns 401 →curl -ffails → fail-open → nothing injected.The endpoint itself is healthy: deployed,
login_requiredacceptsBearer fmcp_keys, andbuild_session_contextreturns{"context": ...}.Fix
hooks.json— command is now justbash "${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-exportedCLAUDE_PLUGIN_OPTION_api_endpoint/api_token/project_idenv 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— bump0.1.1→0.1.2so installs refresh the cache.Verification
bash -nclean./api/plugin/contextreturns 401 only on missing/invalid keys.Tracked on Scribe task 755 (Scribe-as-plugin, Phase 1).
🤖 Generated with Claude Code
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>