mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-14 20:25:26 +02:00
fix: session-activity-tracker never records (CLAUDE_SESSION_ID unset)
session-activity-tracker.js is correctly registered in posttooluse-dispatcher.js's SYNC_HOOKS and fires on every tool call -- but buildActivityRow() only ever read the session id from ECC_SESSION_ID/CLAUDE_SESSION_ID environment variables, which Claude Code does not actually set for hook subprocesses. Every row silently came back null and nothing was ever written to ~/.claude/metrics/tool-usage.jsonl, with no error anywhere. The session id is available in the hook's own stdin JSON payload (input.session_id) in real invocations. This prefers that, keeping the env vars as a fallback. New test added; all 18 existing + new tests pass. Also adds a live Activity view to the capabilities dashboard (scripts/dashboard-web.js) reading that same metrics file, so Skill vs. Agent vs. raw-tool usage is visible in real time instead of requiring a manual tail+jq -- verified live against a real session before this commit. Documents the loopback-only, no-auth design and the standard SSH-port-forwarding pattern for viewing it from another device you control.
This commit is contained in:
@@ -6,6 +6,15 @@
|
||||
* Usage: node scripts/dashboard-web.js [port]
|
||||
* Open http://localhost:3456
|
||||
*
|
||||
* The server only binds to loopback and has no authentication, by design --
|
||||
* it is meant to be viewed on the same machine it runs on. To check it from
|
||||
* another device you control (a tablet, a laptop), use standard SSH local
|
||||
* port forwarding instead of exposing the port itself:
|
||||
* ssh -L 3456:127.0.0.1:3456 <user>@<this-machine> -p <ssh-port>
|
||||
* then open http://127.0.0.1:3456 on the *remote* device. This requires
|
||||
* only SSH access you already control on your own machine -- nothing here
|
||||
* opens the dashboard to the network.
|
||||
*
|
||||
* Contribution: https://github.com/affaan-m/ECC
|
||||
*/
|
||||
|
||||
|
||||
@@ -567,7 +567,13 @@ function buildActivityRow(input, env = process.env) {
|
||||
}
|
||||
|
||||
const toolName = String(input?.tool_name || '').trim();
|
||||
const sessionId = String(env.ECC_SESSION_ID || env.CLAUDE_SESSION_ID || '').trim();
|
||||
// Claude Code does not set CLAUDE_SESSION_ID as an environment variable for
|
||||
// hook subprocesses -- the session id is only available in the hook's own
|
||||
// stdin JSON payload. Prefer that; keep the env vars as a fallback for any
|
||||
// caller that does set them.
|
||||
const sessionId = String(
|
||||
input?.session_id || env.ECC_SESSION_ID || env.CLAUDE_SESSION_ID || ''
|
||||
).trim();
|
||||
if (!toolName || !sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -407,6 +407,28 @@ function runTests() {
|
||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||
}) ? passed++ : failed++);
|
||||
|
||||
(test('reads session_id from stdin JSON when no session env var is set', () => {
|
||||
// Claude Code does not set CLAUDE_SESSION_ID for hook subprocesses in
|
||||
// real invocations -- the session id only arrives via stdin JSON.
|
||||
const tmpHome = makeTempDir();
|
||||
const input = {
|
||||
session_id: 'stdin-session-only',
|
||||
tool_name: 'Read',
|
||||
tool_input: { file_path: path.join(tmpHome, 'foo.txt') },
|
||||
};
|
||||
const result = runScript(input, {
|
||||
...withTempHome(tmpHome),
|
||||
CLAUDE_HOOK_EVENT_NAME: 'PostToolUse',
|
||||
});
|
||||
assert.strictEqual(result.code, 0);
|
||||
|
||||
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'tool-usage.jsonl');
|
||||
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
|
||||
assert.strictEqual(row.session_id, 'stdin-session-only');
|
||||
|
||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||
}) ? passed++ : failed++);
|
||||
|
||||
(test('handles invalid JSON gracefully', () => {
|
||||
const tmpHome = makeTempDir();
|
||||
const invalidInput = 'not valid json {{{';
|
||||
|
||||
Reference in New Issue
Block a user