fix(hooks,test): harden background rewake, fix hook timeout units, stabilize CI teardown

Follow-ups from the PR #175/#176 reviews:

- Rewake helper self-terminates on its own 6h deadline and when orphaned,
  instead of relying on Claude Code to reap the poller
- Rewake marker versioned (V2) with a version-agnostic ownership prefix, so
  future script updates replace older handlers instead of duplicating them;
  regression test covers the V1 to V2 swap
- HOOK_TIMEOUT_MS renamed to HOOK_TIMEOUT_SECONDS = 10: the hook timeout
  field is seconds (the CLI multiplies by 1000), so the curl hooks have
  effectively had a ~2.8h timeout since COD-54
- Test echo PTY switches to raw mode: each input byte echoes exactly once
  (tty line discipline doubled every line and buffered until Enter)
- test/setup.ts: drain in-flight console-log rpc forwards before environment
  teardown (fixes the EnvironmentTeardownError that failed CI twice on the
  merge commit with all 3820 tests passing), clean the temp home on process
  exit (fully-skipped files leaked it), fix the Windows Playwright cache
  fallback path
- test/webview-proxy.test.ts: stop naming the vitest environment directive in
  prose; vitest matches it inside comments and silently ran the whole file
  under the jsdom environment while the comment claimed node
- CLAUDE.md: document the temp-HOME and echo-PTY test isolation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-07-31 08:47:52 +02:00
parent e0226f7186
commit 80e7249a39
8 changed files with 102 additions and 22 deletions
+37 -4
View File
@@ -95,10 +95,10 @@ describe('generateHooksConfig', () => {
expect(notifHooks[0].hooks[0].command).toContain('|| true');
});
it('should set timeout to 10000ms', () => {
it('should set timeout to 10 seconds (hook timeout fields are seconds)', () => {
const config = generateHooksConfig();
const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ timeout: number }> }>;
expect(notifHooks[0].hooks[0].timeout).toBe(10000);
expect(notifHooks[0].hooks[0].timeout).toBe(10);
});
it('should include correct event names in curl payloads', () => {
@@ -199,7 +199,40 @@ describe('writeHooksConfig', () => {
const parsed = JSON.parse(readFileSync(settingsPath, 'utf-8'));
expect(parsed.hooks.PostToolUse).toHaveLength(1);
expect(JSON.stringify(parsed.hooks.PostToolUse)).toContain('CODEMAN_BACKGROUND_REWAKE_V1');
expect(JSON.stringify(parsed.hooks.PostToolUse)).toContain('CODEMAN_BACKGROUND_REWAKE_V');
});
it('should replace an older rewake script version without duplicating it', async () => {
const claudeDir = join(testDir, '.claude');
const settingsPath = join(claudeDir, 'settings.local.json');
mkdirSync(claudeDir, { recursive: true });
// Simulate a case healed by the previous release: current curls (secret present)
// plus a V1 rewake handler. The version bump must swap the handler in place.
const hooks = generateHooksConfig().hooks;
hooks.PostToolUse = [
{
matcher: 'Bash',
hooks: [
{
type: 'command',
command: 'node',
args: ['-e', 'const CODEMAN_BACKGROUND_REWAKE_V1 = true; process.exit(0);'],
asyncRewake: true,
timeout: 21600,
},
],
},
];
writeFileSync(settingsPath, JSON.stringify({ hooks }, null, 2));
await refreshStaleCodemanHooks(testDir);
const parsed = JSON.parse(readFileSync(settingsPath, 'utf-8'));
const serialized = JSON.stringify(parsed.hooks.PostToolUse);
expect(parsed.hooks.PostToolUse).toHaveLength(1);
expect(parsed.hooks.PostToolUse[0].hooks).toHaveLength(1);
expect(serialized).toContain('CODEMAN_BACKGROUND_REWAKE_V2');
expect(serialized).not.toContain('CODEMAN_BACKGROUND_REWAKE_V1');
});
it('should not add rewake hooks to a user-owned hook configuration', async () => {
@@ -841,7 +874,7 @@ describe('Hook Config Generation - Extended', () => {
expect(hook.matcher).toBeDefined();
expect(hook.hooks).toHaveLength(1);
expect(hook.hooks[0].type).toBe('command');
expect(hook.hooks[0].timeout).toBe(10000);
expect(hook.hooks[0].timeout).toBe(10);
expect(hook.hooks[0].command).toBeTruthy();
}
});