fix: self-install deps from script location, remove SessionStart hook#13
Conversation
Move npm install into scan-tool-result.mjs using import.meta.url to resolve plugin root on disk. This removes the dependency on CLAUDE_PLUGIN_ROOT being set during SessionStart, which was unreliable and caused silent install failures. Deps are now installed on first PostToolUse scan, with a fast existsSync check skipping the install on all subsequent runs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR makes the Defender dependency installation self-contained within the PostToolUse scanner script, removing the previously unreliable SessionStart hook-based install step.
Changes:
- Add first-run dependency self-install logic to
scripts/scan-tool-result.mjsby resolving the plugin root from the script location. - Remove the
SessionStarthook fromhooks/hooks.jsonso installs no longer happen during session initialization.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| scripts/scan-tool-result.mjs | Resolves plugin root, self-installs dependencies on first run, and then loads @stackone/defender via createRequire. |
| hooks/hooks.json | Removes the SessionStart hook that previously ran npm install. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
MCP tools (Gmail, Notion, etc.) return arbitrary JSON objects rather
than a string or a WebFetch-style {result} wrapper. Previously the
script extracted raw.result ?? raw.output ?? "" which resolved to an
empty string for all MCP responses, silently skipping the scan.
Now falls back to JSON.stringify(raw) so all text fields (body,
snippet, headers, …) are included. Confirmed blocking tier2Score 0.997
on a prompt-injection email via gmail_read_message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/scan-tool-result.mjs">
<violation number="1" location="scripts/scan-tool-result.mjs:55">
P2: `JSON.stringify(raw)` on arbitrary objects can throw (e.g., circular refs/BigInt), which can crash the hook before scan handling.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
2 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/scan-tool-result.mjs">
<violation number="1" location="scripts/scan-tool-result.mjs:21">
P1: Avoid trusting `CLAUDE_PLUGIN_ROOT` for plugin root resolution here; prefer the script-derived path so install/load cannot be redirected by environment tampering.</violation>
<violation number="2" location="scripts/scan-tool-result.mjs:33">
P2: On install failure the script exits 0 with no output, silently disabling the entire tool-output scanner. At minimum emit a warning to stderr so users can diagnose why Defender isn't running.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- Drop CLAUDE_PLUGIN_ROOT fallback for plugin root resolution; always derive from import.meta.url so the path cannot be redirected by env var tampering (P1) - Emit stderr warning on npm install failure instead of silently exiting, so users can diagnose why the scanner is disabled (P2) - Wrap JSON.stringify(raw) in try/catch to handle circular refs or BigInt values in MCP tool responses without crashing the hook (P2) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
release-please v17 dropped the generic release type. Switch to node which natively handles package.json and still supports extra-files with jsonpath for the plugin JSON files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
0 issues found across 2 files (changes from recent commits).
Requires human review: Changes to dependency installation (moving npm install to the script runtime) and removing the SessionStart hook modify the plugin's lifecycle and warrant human review.
Use process.stderr.write for consistency with the rest of the script. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
npm installfromSessionStarthook intoscan-tool-result.mjsitself, usingimport.meta.urlto resolve the plugin root on diskSessionStarthook fromhooks/hooks.jsonentirelynode_modules/@stackone/defenderdoesn't exist; subsequent invocations skip it via a synchronousexistsSynccheck (essentially zero overhead)Why
The
SessionStarthook was unreliable:CLAUDE_PLUGIN_ROOTis not guaranteed to be set at that phase, sonpm installwould silently fail or install to the wrong directory. Moving install into the scan script itself means it uses a path derived fromimport.meta.url— always correct regardless of env vars.Test plan
node_modules/@stackone/defender, trigger a tool call (e.g. Bashecho hi) — defender installs automatically and scan runsexistsSyncreturns true, install is skipped, scan is near-instant🤖 Generated with Claude Code
Summary by cubic
Self-install plugin deps from the scan script using its own path (no env var) and expand scanning to MCP tool responses; warn on install failures. Also remove the
SessionStarthook so@stackone/defenderinstalls on first scan only.Bug Fixes
import.meta.url; dropCLAUDE_PLUGIN_ROOT.npm install --prefix "<pluginRoot>" --silent --no-audit --no-fund(120s timeout); on failure, emit stderr warning and skip scan (exit 0); noSessionStarthook.raw.result/raw.outputorJSON.stringify(raw)(try/catch), and log serialization errors tostderrto avoid silent skips.Dependencies
release-pleaseto thenoderelease type.Written for commit 95d91a3. Summary will update on new commits.