mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-21 00:58:07 +02:00
* fix(hooks): keep silent hook paths silent * fix(hooks): harden stream failure handling * fix(hooks): settle interrupted input streams * test(hooks): name stream input limits * test(hooks): isolate PostToolUse dispatcher fixtures
102 lines
3.7 KiB
JavaScript
102 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
/**
|
|
* session-start-bootstrap.js
|
|
*
|
|
* Bootstrap loader for the ECC SessionStart hook.
|
|
*
|
|
* Problem this solves: the previous approach embedded this logic as an inline
|
|
* `node -e "..."` string inside hooks.json. Characters like `!` (used in
|
|
* `!org.isDirectory()`) can trigger bash history expansion or other shell
|
|
* interpretation issues depending on the environment, causing
|
|
* "SessionStart:startup hook error" to appear in the Claude Code CLI header.
|
|
*
|
|
* By extracting to a standalone file, the shell never sees the JavaScript
|
|
* source and the `!` characters are safe. Behaviour is otherwise identical.
|
|
*
|
|
* How it works:
|
|
* 1. Reads the raw JSON event from stdin (passed by Claude Code).
|
|
* 2. Resolves the ECC plugin root directory (via CLAUDE_PLUGIN_ROOT env var
|
|
* or a set of well-known fallback paths).
|
|
* 3. Delegates to `scripts/hooks/run-with-flags.js` with the `session:start`
|
|
* event, which applies hook-profile gating and then runs session-start.js.
|
|
* 4. Passes stdout/stderr through and forwards the child exit code.
|
|
* 5. If the plugin root cannot be found, emits a warning and no stdout so
|
|
* Claude Code can continue normally without duplicating the event.
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { spawnSync } = require('child_process');
|
|
const { resolveEccRoot } = require('../lib/resolve-ecc-root');
|
|
const { readStdinRaw, resolveMaxStdin } = require('./hook-input');
|
|
const { exitAfterFlush } = require('./lifecycle-hook-bootstrap');
|
|
|
|
async function main() {
|
|
const maxStdin = resolveMaxStdin(process.env.ECC_HOOK_INPUT_MAX_BYTES, {
|
|
writeDiagnostic: message => process.stderr.write(message)
|
|
});
|
|
const { raw, truncated } = await readStdinRaw(process.stdin, {
|
|
maxStdin,
|
|
truncated: /^(1|true|yes)$/i.test(
|
|
String(process.env.ECC_HOOK_INPUT_TRUNCATED_UPSTREAM || '')
|
|
)
|
|
});
|
|
if (truncated) {
|
|
process.stderr.write(`[SessionStart] stdin exceeded ${maxStdin} bytes; forwarded a bounded prefix\n`);
|
|
}
|
|
|
|
// Path (relative to plugin root) to the hook runner
|
|
const rel = path.join('scripts', 'hooks', 'run-with-flags.js');
|
|
|
|
// Resolve the ECC plugin root via the shared resolver, probing for the runner
|
|
// so a valid root is one that actually contains run-with-flags.js.
|
|
const root = resolveEccRoot({ probe: rel });
|
|
const script = path.join(root, rel);
|
|
|
|
if (fs.existsSync(script)) {
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
[script, 'session:start', 'scripts/hooks/session-start.js', 'minimal,standard,strict'],
|
|
{
|
|
input: raw,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
ECC_HOOK_INPUT_MAX_BYTES: String(maxStdin),
|
|
ECC_HOOK_INPUT_TRUNCATED_UPSTREAM: truncated ? '1' : '0'
|
|
},
|
|
cwd: process.cwd(),
|
|
timeout: 30000,
|
|
}
|
|
);
|
|
|
|
const stdout = typeof result.stdout === 'string' ? result.stdout : '';
|
|
let stderr = typeof result.stderr === 'string' ? result.stderr : '';
|
|
let exitCode = Number.isInteger(result.status) ? result.status : 0;
|
|
|
|
if (result.error || result.status === null || result.signal) {
|
|
const reason = result.error
|
|
? result.error.message
|
|
: result.signal
|
|
? 'signal ' + result.signal
|
|
: 'missing exit status';
|
|
stderr += '[SessionStart] ERROR: session-start hook failed: ' + reason + '\n';
|
|
exitCode = 1;
|
|
}
|
|
|
|
exitAfterFlush(stdout, stderr, exitCode);
|
|
return;
|
|
}
|
|
|
|
process.stderr.write(
|
|
'[SessionStart] WARNING: could not resolve ECC plugin root; skipping session-start hook\n'
|
|
);
|
|
}
|
|
|
|
main().catch(error => {
|
|
process.stderr.write(`[SessionStart] bootstrap failed: ${error.message}\n`);
|
|
process.exitCode = 0;
|
|
});
|