Skip to content

fix(defender): give each Defender plugin its own daemon - #39

Merged
glebedel merged 2 commits into
mainfrom
fix/defender-daemon-identity-per-plugin
Sep 23, 2026
Merged

glebedel merged 2 commits into
mainfrom
fix/defender-daemon-identity-per-plugin

Conversation

@glebedel

@glebedel glebedel commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #37. The two Defender plugins shared one background daemon, so installing both made each one kill the other's daemon on every scan.

  • New scripts/daemon-paths.mjs per plugin. The hook client and the daemon both import it, so they always agree on the socket and state files.
  • The Claude Code plugin keeps its current paths (~/.claude/defender.*), so existing installs see no change.
  • Antigravity moves to ~/.claude/defender-antigravity.*. Its README no longer promises a shared daemon.

Evidence

Both plugins alternating over 6 scans, in a clean HOME:

Daemon spawns Kills Scan after warm-up
main 6 5 0.8s
this PR 2 0 0.4s

Cold start measured 0.8 to 2.5s on an M-series Mac, well under the 5s scan budget. So the fail-open risk in #37 looks smaller than estimated, though slower machines may differ.

Trade-off

With both plugins installed there are now two daemons and two copies of the model in memory. The alternative was aligning versions so one daemon could serve both, but the dependency stamp added in #36 differs between the plugins anyway.

Test plan

  • npm test in both plugins: 15 and 12 pass
  • End-to-end: both hooks alternating, daemons reused, zero kills (table above)
  • Same run on main reproduces the thrash

🤖 Generated with Claude Code


Summary by cubic

Gives each Defender plugin its own background daemon so installing both no longer makes them kill each other's daemon on every scan. stackone-defender and stackone-defender-antigravity previously shared ~/.claude/defender.sock plus the same state, lock, and log files, but they pin different Defender versions and each client replaces any daemon that doesn't match its own dependency tree.

The socket and state paths now live in a new scripts/daemon-paths.mjs per plugin, imported by both the hook client and the daemon so they can't disagree. The Claude Code plugin keeps its existing ~/.claude/defender.* paths, so current installs see no change; Antigravity moves to ~/.claude/defender-antigravity.* and its README no longer claims a shared daemon.

Trade-off

  • With both plugins installed there are now two daemons and two copies of the model in memory. Aligning versions so one daemon could serve both would require matching dependency stamps, which differ between the plugins.

Benchmarks

Written for commit 42540c4. Summary will update on new commits.

Review in cubic

stackone-defender and stackone-defender-antigravity both used
~/.claude/defender.sock and the same state, lock and log files. They pin
different Defender versions (0.8.2 vs 0.7.0), and each client replaces any
daemon that doesn't match its own tree, so with both installed every scan
killed the other plugin's daemon and cold-started its own.

The paths now live in scripts/daemon-paths.mjs, imported by the hook client
and the daemon so the two can't disagree. The Claude Code plugin keeps its
existing file names, so current installs see no change. Antigravity moves to
defender-antigravity.*. Its README no longer promises a shared daemon.

Measured with both plugins alternating over 6 scans in a clean HOME:
  main:  6 daemon spawns, 5 kills, 0.8s per scan after warm-up
  fixed: 2 spawns, 0 kills, 0.4s per scan, both daemons reused
Cold start on an M-series Mac was 0.8-2.5s, well under the 5s scan budget.
That lowers the fail-open risk raised in #37, but slower machines may differ.

Cost: two model copies in memory when both plugins are installed.

Fixes #37

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 23, 2026 14:59

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 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="plugins/security/stackone-defender/scripts/daemon-paths.mjs">

<violation number="1" location="plugins/security/stackone-defender/scripts/daemon-paths.mjs:13">
P3: This entire script set is vendored twice: `plugins/security/stackone-defender-antigravity/scripts/daemon-paths.mjs` is byte-identical to this file except for `NAME = "defender-antigravity"`, and the two `scan-tool-result.mjs`/`defender-daemon.mjs` copies are likewise duplicated. Every future change to the daemon client protocol, path layout, or log handling must now be applied in two trees, and a third Defender variant would need a third copy. Consider generating `NAME` from the plugin's package identity and sharing the scripts between the two plugin trees at packaging time (or a script that syncs them) so the paths can't drift.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread plugins/security/stackone-defender-antigravity/README.md
import { join } from "path";

const DIR = join(homedir(), ".claude");
const NAME = "defender";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This entire script set is vendored twice: plugins/security/stackone-defender-antigravity/scripts/daemon-paths.mjs is byte-identical to this file except for NAME = "defender-antigravity", and the two scan-tool-result.mjs/defender-daemon.mjs copies are likewise duplicated. Every future change to the daemon client protocol, path layout, or log handling must now be applied in two trees, and a third Defender variant would need a third copy. Consider generating NAME from the plugin's package identity and sharing the scripts between the two plugin trees at packaging time (or a script that syncs them) so the paths can't drift.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/security/stackone-defender/scripts/daemon-paths.mjs, line 13:

<comment>This entire script set is vendored twice: `plugins/security/stackone-defender-antigravity/scripts/daemon-paths.mjs` is byte-identical to this file except for `NAME = "defender-antigravity"`, and the two `scan-tool-result.mjs`/`defender-daemon.mjs` copies are likewise duplicated. Every future change to the daemon client protocol, path layout, or log handling must now be applied in two trees, and a third Defender variant would need a third copy. Consider generating `NAME` from the plugin's package identity and sharing the scripts between the two plugin trees at packaging time (or a script that syncs them) so the paths can't drift.</comment>

<file context>
@@ -0,0 +1,20 @@
+import { join } from "path";
+
+const DIR = join(homedir(), ".claude");
+const NAME = "defender";
+
+export const SOCKET_PATH = join(DIR, `${NAME}.sock`);
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point, but I'm keeping it out of this PR.

The duplication predates this change. The two plugins already carry separate copies of scan-tool-result.mjs, defender-daemon.mjs and deps-fingerprint.mjs, because each is installed on its own into a different host with its own node_modules. daemon-paths.mjs follows that existing layout, and the only intended difference between the two copies is NAME.

Sharing the scripts at packaging time, or adding a sync check, is worth doing, but it's a change to how both plugins are built. It should be reviewed on its own rather than folded into a bug fix.

Comment thread plugins/security/stackone-defender-antigravity/README.md
…ty README

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Requires human review: This PR changes daemon path management to per-plugin paths: approximates correct behavior but contains inconsistencies that may break daemon initialization.

Re-trigger cubic

@glebedel
glebedel merged commit 3bd77cc into main Sep 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Defender plugins share one daemon identity, so installing both makes them kill each other

2 participants