@remnic/plugin-claude-code 9.63.5 → 9.63.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -21,6 +21,9 @@ const path = require("node:path");
|
|
|
21
21
|
const { spawn } = require("node:child_process");
|
|
22
22
|
|
|
23
23
|
const RUNNER = path.join(__dirname, "remnic-cc-hook.cjs");
|
|
24
|
+
// Shared runner source (issue #2483): implementation-detail assertions read
|
|
25
|
+
// the core, while behavioral tests spawn the wrapper entry (RUNNER).
|
|
26
|
+
const CORE = path.join(__dirname, "remnic-hook-core.cjs");
|
|
24
27
|
|
|
25
28
|
function startServer(handler) {
|
|
26
29
|
const calls = [];
|
|
@@ -76,6 +79,12 @@ function runHook(event, input, { port, home, env = {} } = {}) {
|
|
|
76
79
|
// Internal worker-propagation channel, not a user credential. Pinned
|
|
77
80
|
// so an inherited value cannot reach the detached observe worker.
|
|
78
81
|
REMNIC_HOOK_TOKEN: "",
|
|
82
|
+
// Clear daemon URL env so a developer shell with REMNIC_DAEMON_URL
|
|
83
|
+
// set can't route tests away from the mock server (the shared core
|
|
84
|
+
// honors it since issue #2483). Tests that WANT a daemon URL
|
|
85
|
+
// override it via env.extra (which spreads last).
|
|
86
|
+
REMNIC_DAEMON_URL: "",
|
|
87
|
+
ENGRAM_DAEMON_URL: "",
|
|
79
88
|
...env.extra,
|
|
80
89
|
},
|
|
81
90
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -167,6 +176,44 @@ test("session-start: health probe carries the bearer token (auth-gated daemons)"
|
|
|
167
176
|
}
|
|
168
177
|
});
|
|
169
178
|
|
|
179
|
+
test("session-start: REMNIC_DAEMON_URL routes recall to the remote base URL (#2483)", async () => {
|
|
180
|
+
const home = mkHome();
|
|
181
|
+
const { server, port, calls } = await startServer((req, res) => {
|
|
182
|
+
if (req.url === "/engram/v1/health") return res.writeHead(200).end("ok");
|
|
183
|
+
if (req.url === "/engram/v1/recall") {
|
|
184
|
+
return res.writeHead(200, { "Content-Type": "application/json" }).end(
|
|
185
|
+
JSON.stringify({ context: "remote recall", count: 1, mode: "auto" }),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
res.writeHead(404).end();
|
|
189
|
+
});
|
|
190
|
+
try {
|
|
191
|
+
// A full REMNIC_DAEMON_URL must take precedence over the (wrong) PORT —
|
|
192
|
+
// the same remote/central-daemon contract the Codex runner has had since
|
|
193
|
+
// #1571. Issue #2483: the Claude Code hook inherits it via the shared
|
|
194
|
+
// core, so a Claude Code host can also reach a remote daemon over
|
|
195
|
+
// Tailscale/LAN/VPN (plain or TLS).
|
|
196
|
+
const { json } = await runHook(
|
|
197
|
+
"session-start",
|
|
198
|
+
{ session_id: "remote-1", cwd: home },
|
|
199
|
+
{
|
|
200
|
+
port: 1, // deliberately unused; daemon URL wins
|
|
201
|
+
home,
|
|
202
|
+
env: { extra: { REMNIC_DAEMON_URL: `http://127.0.0.1:${port}` } },
|
|
203
|
+
},
|
|
204
|
+
);
|
|
205
|
+
assert.equal(json.continue, true);
|
|
206
|
+
assert.match(json.hookSpecificOutput.additionalContext, /remote recall/);
|
|
207
|
+
assert.ok(
|
|
208
|
+
calls.some((c) => c.url === "/engram/v1/recall"),
|
|
209
|
+
"recall reached the REMNIC_DAEMON_URL host",
|
|
210
|
+
);
|
|
211
|
+
} finally {
|
|
212
|
+
server.close();
|
|
213
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
170
217
|
test("recall body targets REMNIC_NAMESPACE when set, and omits namespace when unset", async () => {
|
|
171
218
|
const mk = () =>
|
|
172
219
|
startServer((req, res) => {
|
|
@@ -471,7 +518,7 @@ test("post-tool-observe: worker payload travels via STDIN, not the environment",
|
|
|
471
518
|
// (big file edits) would E2BIG; the worker reads stdin instead. We assert
|
|
472
519
|
// the source rather than running an E2BIG payload because reproducing the
|
|
473
520
|
// limit cross-platform is impractical.
|
|
474
|
-
const src = fs.readFileSync(
|
|
521
|
+
const src = fs.readFileSync(CORE, "utf8");
|
|
475
522
|
assert.doesNotMatch(
|
|
476
523
|
src,
|
|
477
524
|
/REMNIC_HOOK_INPUT:\s*rawInput/,
|
|
@@ -804,7 +851,7 @@ test("plugin.json: manifest conforms to the Claude Code plugin schema (author is
|
|
|
804
851
|
});
|
|
805
852
|
|
|
806
853
|
test("runner source: payload fields never reach a shell — spawn uses fixed argv (#1518 guard shell interpolation)", () => {
|
|
807
|
-
const src = fs.readFileSync(
|
|
854
|
+
const src = fs.readFileSync(CORE, "utf8");
|
|
808
855
|
// Every spawn/spawnSync must use a fixed literal argument array, never a
|
|
809
856
|
// string command, so a payload value (session id, cwd, transcript path,
|
|
810
857
|
// prompt, tool name) cannot achieve command injection.
|
|
@@ -831,7 +878,7 @@ test("runner source: payload fields never reach a shell — spawn uses fixed arg
|
|
|
831
878
|
});
|
|
832
879
|
|
|
833
880
|
test("runner source: stdin is the single payload source — no env-var override", () => {
|
|
834
|
-
const src = fs.readFileSync(
|
|
881
|
+
const src = fs.readFileSync(CORE, "utf8");
|
|
835
882
|
assert.doesNotMatch(
|
|
836
883
|
src,
|
|
837
884
|
/process\.env\.REMNIC_HOOK_INPUT/,
|
|
@@ -840,7 +887,7 @@ test("runner source: stdin is the single payload source — no env-var override"
|
|
|
840
887
|
});
|
|
841
888
|
|
|
842
889
|
test("runner source: path inputs are type-validated before use (#1518 validate path types)", () => {
|
|
843
|
-
const src = fs.readFileSync(
|
|
890
|
+
const src = fs.readFileSync(CORE, "utf8");
|
|
844
891
|
// cwd / transcript_path are coerced via `input.X || ""` so an unexpected
|
|
845
892
|
// non-string payload field (number, object, array) cannot reach fs / git
|
|
846
893
|
// and throw an opaque error or, worse, be coerced by Node to a path.
|