Skip to content

fix(hooks): complete hook system fix — 8 bugs resolved#551

Open
riaworks wants to merge 2 commits intoSynkraAI:mainfrom
riaworks:fix/hook-by-riaworks
Open

fix(hooks): complete hook system fix — 8 bugs resolved#551
riaworks wants to merge 2 commits intoSynkraAI:mainfrom
riaworks:fix/hook-by-riaworks

Conversation

@riaworks
Copy link
Contributor

@riaworks riaworks commented Mar 4, 2026

Summary

Complete fix for the AIOS hook system on Windows. Resolves 8 interconnected bugs that caused UserPromptSubmit hook error on every prompt.

Bugs Fixed

# Bug Root Cause Fix
1 Hook error on every prompt precompact-session-digest.cjs registered as UserPromptSubmit (wrong event) Remove from UserPromptSubmit in settings.json
2 Hook output rejected by Claude Code Missing hookEventName in JSON output Add hookEventName: 'UserPromptSubmit' to buildHookOutput()
3 stdout cut on Windows process.exit(0) kills pipe before flush Remove process.exit(), let Node exit naturally
4 Session never persisted createSession() never called in hook flow Call createSession() when loadSession() returns null
5 PreCompact runner not found Path looks in .aios-core/hooks/ but runner is in node_modules/aios-core/.aios-core/hooks/ Add node_modules/aios-core/ to path
6 $CLAUDE_PROJECT_DIR fails on Windows Bash variable doesn't expand in cmd.exe Use relative path node .claude/hooks/synapse-engine.cjs
7 timeout: 10 kills hook prematurely 10s too low, Claude Code manages timeouts natively Remove timeout from settings.json
8 code-intel-pretool.cjs referenced but missing File doesn't exist in .claude/hooks/ Remove from settings.json

Files Changed

  • .claude/settings.json — clean hook registration (only synapse-engine in UserPromptSubmit)
  • .claude/hooks/synapse-engine.cjs — remove process.exit(), let Node exit naturally
  • .claude/hooks/precompact-session-digest.cjs — fix runner path to node_modules/aios-core/
  • .aios-core/core/synapse/runtime/hook-runtime.js — add hookEventName, wire createSession(), add orphan .tmp cleanup

Test plan

  • Hook runs without error on Windows 11
  • Synapse rules injected successfully (25 context rules + Constitution)
  • Session persisted to .synapse/sessions/
  • No UserPromptSubmit hook error on fresh Claude Code session
  • Hook debug log confirms OK execution

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced temporary session file cleanup to remove orphaned files and reduce disk usage
    • Improved session creation with fallback logic to ensure valid session state
  • Chores

    • Removed timeout limits from hook configurations for more reliable execution
    • Simplified hook command paths to use relative references
    • Refactored hook process exit handling to allow natural termination

riaworks and others added 2 commits March 2, 2026 17:15
…l.json

The installer generated platform-specific hook commands that caused issues:
- Windows: absolute paths with escaped backslashes (machine-dependent, fragile)
- Unix: $CLAUDE_PROJECT_DIR variable (known bugs GH #6023/#5814)

Both approaches led to UserPromptSubmit hook errors in installed projects.

Changes:
- Use relative path `node .claude/hooks/<file>` on all platforms
- Remove hardcoded `timeout: 10` from HOOK_EVENT_MAP and generated settings
  (Claude Code manages hook timeouts natively, each hook has internal safety)
- Remove unused `isWindows` and `hookFilePath` variables
- Update tests to reflect timeout removal

The HOOK_EVENT_MAP event routing (MIS-3.1) remains unchanged — precompact
is correctly mapped to PreCompact, not UserPromptSubmit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- settings.json: remove precompact from UserPromptSubmit (wrong event type),
  remove code-intel-pretool.cjs (file doesn't exist), use relative paths,
  remove timeout override
- hook-runtime.js: add hookEventName to buildHookOutput (required by Claude Code),
  wire createSession() for session persistence, add cleanOrphanTmpFiles()
- synapse-engine.cjs: remove process.exit() (kills stdout pipe on Windows),
  let Node exit naturally
- precompact-session-digest.cjs: fix runner path to node_modules/aios-core/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 4, 2026

@riaworks is attempting to deploy a commit to the Pedro Valério Lopez's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Walkthrough

The pull request refactors hook registration and execution by removing timeout-based process exit handling, simplifying hook paths to relative resolution, adding session creation fallback logic, and implementing orphaned temporary file cleanup on first prompt. Hook configurations are updated across installer, settings, and tests to reflect these changes.

Changes

Cohort / File(s) Summary
Hook Runtime & Session Management
.aios-core/core/synapse/runtime/hook-runtime.js
Added cleanOrphanTmpFiles() function to remove stale temporary files; integrated session creation fallback logic when loadSession returns null; augmented hook output with hookEventName: 'UserPromptSubmit'.
Hook Execution Engine
.claude/hooks/synapse-engine.cjs
Removed safeExit helper and process exit logic; modified run flow to allow natural Node process termination instead of explicit exit calls; expanded exports to include readStdin, main, run, and HOOK_TIMEOUT_MS.
Hook Path Resolution & Configuration
.claude/hooks/precompact-session-digest.cjs, .claude/settings.json
Updated precompact runner path to resolve through node_modules/aios-core; simplified UserPromptSubmit hook command to relative path; removed PreToolUse and PreCompact hook blocks and timeout field.
Installer Configuration
packages/installer/src/wizard/ide-config-generator.js
Removed timeout property from all hook configurations (HOOK_EVENT_MAP, DEFAULT_HOOK_CONFIG); replaced absolute/Windows-specific path logic with consistent relative path resolution (node .claude/hooks/{hookFileName}); simplified hook registration to omit timeout field.
Test Updates
packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js
Updated assertions to expect undefined timeout values instead of numeric 10 across hook config entries and DEFAULT_HOOK_CONFIG test cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • SynkraAI/aios-core#127 — Related modifications to Synapse hook entry/registration and timeout/path handling in hook configurations.
  • SynkraAI/aios-core#114 — Introduces createSession and loadSession APIs in session-manager that are now integrated into hook-runtime.js cleanup logic.
  • SynkraAI/aios-core#136 — Related changes to Synapse hook execution flow, specifically removal of safeExit and process exit behavior.

Suggested labels

core, installer, tests, area: synapse, area: installer

Suggested reviewers

  • oalanicolas
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: fixing 8 bugs in the hook system. It is specific, concise, and directly reflects the changes across multiple hook-related files and configurations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
.aios-core/core/synapse/runtime/hook-runtime.js 73.68% 4 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.aios-core/core/synapse/runtime/hook-runtime.js (1)

146-149: ⚠️ Potential issue | 🟠 Major

CI blocker: regenerate manifest for this core file change.

Pipeline reports core/synapse/runtime/hook-runtime.js hash mismatch. Please run npm run generate:manifest and commit updated manifest artifacts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/synapse/runtime/hook-runtime.js around lines 146 - 149, CI
reports a hash mismatch for core/synapse/runtime/hook-runtime.js after the
change exporting resolveHookRuntime and buildHookOutput; run the manifest
regeneration step (npm run generate:manifest), verify the generated manifest
artifacts include the updated hash for this file, and commit the updated
manifest files (and any related generated artifacts) so the pipeline picks up
the new core/synapse/runtime/hook-runtime.js export changes.
.claude/hooks/precompact-session-digest.cjs (1)

58-67: ⚠️ Potential issue | 🔴 Critical

The hardcoded runner path is incorrect and will fail at runtime; fix to actual location and add error handling.

The path constructed in lines 58–67 points to node_modules/aios-core/.aios-core/hooks/unified/runners/precompact-runner.js, but that location does not exist in this repository. The actual runner is located at .aios-core/hooks/unified/runners/precompact-runner.js at the project root. The unguarded require(runnerPath) on line 81 will throw immediately when this hook runs, breaking the entire precompaction flow.

Update the path to the correct location and guard the require with try-catch or early-exit to prevent the hook from blocking critical operations:

Recommended fix
  const path = require('path');
+const fs = require('fs');
@@
  const runnerPath = path.join(
    PROJECT_ROOT,
-    'node_modules',
-    'aios-core',
     '.aios-core',
     'hooks',
     'unified',
     'runners',
     'precompact-runner.js',
   );
+
+  if (!fs.existsSync(runnerPath)) {
+    console.warn(`Precompact runner not found at ${runnerPath}; skipping hook`);
+    return;
+  }
@@
-  const { onPreCompact } = require(runnerPath);
-  await onPreCompact(context);
+  try {
+    const { onPreCompact } = require(runnerPath);
+    if (typeof onPreCompact === 'function') {
+      await onPreCompact(context);
+    }
+  } catch (error) {
+    console.error(`Failed to execute precompact hook: ${error.message}`);
+    // Continue without precompaction rather than block the flow
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks/precompact-session-digest.cjs around lines 58 - 67, The
constructed runnerPath currently points to node_modules/... and will not exist;
update the path construction for runnerPath to point to the root-level
.aios-core/hooks/unified/runners/precompact-runner.js (use PROJECT_ROOT joined
with '.aios-core','hooks','unified','runners','precompact-runner.js') and wrap
the dynamic load (the require(runnerPath) call) in a try-catch or guard that
logs an informative warning and early-returns so the hook does not throw and
block precompaction; reference and update the runnerPath constant and the
require(runnerPath) usage to implement this error handling.
🧹 Nitpick comments (1)
.aios-core/core/synapse/runtime/hook-runtime.js (1)

135-141: Update output JSDoc to match the new payload shape.

The return type annotation still documents only additionalContext, but Line 140 now includes hookEventName.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/synapse/runtime/hook-runtime.js around lines 135 - 141, The
JSDoc for buildHookOutput is outdated: the returned object now includes
hookSpecificOutput.hookEventName as well as additionalContext; update the
function's `@returns` annotation to reflect the new payload shape (e.g., return
type should document hookSpecificOutput: { hookEventName: string,
additionalContext: string }) so the documented contract matches the actual
return value from buildHookOutput and references both hookEventName and
additionalContext.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.aios-core/core/synapse/runtime/hook-runtime.js:
- Around line 16-27: cleanOrphanTmpFiles currently unconditionally deletes any
filename containing ".tmp." which can remove temp files still in use by
concurrent atomic writers; update the function (cleanOrphanTmpFiles and the
similar logic used elsewhere) to only target strictly-matching temp filenames
(e.g., a regex like /\.tmp\.[^.]+$/ or the project's temp-naming convention) and
add an age threshold check using fs.statSync(path).mtime (skip files younger
than a safe window, e.g., 1–5 minutes) before unlinking; also catch and log stat
errors and continue so locked/permissioned files are skipped safely.

---

Outside diff comments:
In @.aios-core/core/synapse/runtime/hook-runtime.js:
- Around line 146-149: CI reports a hash mismatch for
core/synapse/runtime/hook-runtime.js after the change exporting
resolveHookRuntime and buildHookOutput; run the manifest regeneration step (npm
run generate:manifest), verify the generated manifest artifacts include the
updated hash for this file, and commit the updated manifest files (and any
related generated artifacts) so the pipeline picks up the new
core/synapse/runtime/hook-runtime.js export changes.

In @.claude/hooks/precompact-session-digest.cjs:
- Around line 58-67: The constructed runnerPath currently points to
node_modules/... and will not exist; update the path construction for runnerPath
to point to the root-level .aios-core/hooks/unified/runners/precompact-runner.js
(use PROJECT_ROOT joined with
'.aios-core','hooks','unified','runners','precompact-runner.js') and wrap the
dynamic load (the require(runnerPath) call) in a try-catch or guard that logs an
informative warning and early-returns so the hook does not throw and block
precompaction; reference and update the runnerPath constant and the
require(runnerPath) usage to implement this error handling.

---

Nitpick comments:
In @.aios-core/core/synapse/runtime/hook-runtime.js:
- Around line 135-141: The JSDoc for buildHookOutput is outdated: the returned
object now includes hookSpecificOutput.hookEventName as well as
additionalContext; update the function's `@returns` annotation to reflect the new
payload shape (e.g., return type should document hookSpecificOutput: {
hookEventName: string, additionalContext: string }) so the documented contract
matches the actual return value from buildHookOutput and references both
hookEventName and additionalContext.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3004a34f-9685-4bb9-bb51-ee0e4b06161b

📥 Commits

Reviewing files that changed from the base of the PR and between 1fcfd4c and 7fe7a98.

📒 Files selected for processing (6)
  • .aios-core/core/synapse/runtime/hook-runtime.js
  • .claude/hooks/precompact-session-digest.cjs
  • .claude/hooks/synapse-engine.cjs
  • .claude/settings.json
  • packages/installer/src/wizard/ide-config-generator.js
  • packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js

Comment on lines +16 to +27
function cleanOrphanTmpFiles(sessionsDir) {
try {
const files = fs.readdirSync(sessionsDir);
let removed = 0;
for (const f of files) {
if (f.includes('.tmp.')) {
try { fs.unlinkSync(path.join(sessionsDir, f)); removed++; }
catch (_) { /* locked/permissions — skip */ }
}
}
return removed;
} catch (_) { return 0; }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid deleting live .tmp files during concurrent atomic writes.

cleanOrphanTmpFiles() deletes every file containing .tmp. immediately. Concurrent hook processes can still be using fresh temp files, causing rename failures/data loss. Add an age threshold (and stricter filename match) before deletion.

🛠️ Suggested safeguard
+const ORPHAN_TMP_MIN_AGE_MS = 60 * 1000;
@@
 function cleanOrphanTmpFiles(sessionsDir) {
   try {
     const files = fs.readdirSync(sessionsDir);
     let removed = 0;
     for (const f of files) {
-      if (f.includes('.tmp.')) {
+      if (/\.tmp\./.test(f)) {
+        const filePath = path.join(sessionsDir, f);
         try { fs.unlinkSync(path.join(sessionsDir, f)); removed++; }
         catch (_) { /* locked/permissions — skip */ }
+        try {
+          const stat = fs.statSync(filePath);
+          if ((Date.now() - stat.mtimeMs) < ORPHAN_TMP_MIN_AGE_MS) continue;
+          fs.unlinkSync(filePath);
+          removed++;
+        } catch (_) { /* locked/permissions/stat failure — skip */ }
       }
     }
     return removed;
   } catch (_) { return 0; }
 }

As per coding guidelines ".aios-core/core/**: Check for race conditions in orchestration modules (lock-manager, session-state)."

Also applies to: 112-120

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.aios-core/core/synapse/runtime/hook-runtime.js around lines 16 - 27,
cleanOrphanTmpFiles currently unconditionally deletes any filename containing
".tmp." which can remove temp files still in use by concurrent atomic writers;
update the function (cleanOrphanTmpFiles and the similar logic used elsewhere)
to only target strictly-matching temp filenames (e.g., a regex like
/\.tmp\.[^.]+$/ or the project's temp-naming convention) and add an age
threshold check using fs.statSync(path).mtime (skip files younger than a safe
window, e.g., 1–5 minutes) before unlinking; also catch and log stat errors and
continue so locked/permissioned files are skipped safely.

Copy link
Contributor

@nikolasdehor nikolasdehor left a comment

Choose a reason for hiding this comment

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

Excelente diagnóstico — os 8 bugs documentados são reais e a abordagem de correção é sólida. Revisando em relação à issue #544 (tier-based hook installation) que acompanhei: as correções aqui são complementares e não conflitam com aquele escopo.

Bug 1 — precompact-session-digest.cjs no evento errado:
Correto remover do UserPromptSubmit. O hook de digestão de sessão pertence ao PreCompact — o mapeamento estava invertido.

Bug 2 — hookEventName ausente no output:
Adição de hookEventName: 'UserPromptSubmit' em buildHookOutput() é necessária conforme a spec do Claude Code. Sem isso, o Claude Code não consegue rotear a resposta corretamente.

Bug 3 — process.exit(0) cortando stdout no Windows:
A solução de deixar o Node encerrar naturalmente (via timer.unref() + then/catch vazios) é a abordagem correta. O safeExit() com check de JEST_WORKER_ID era uma gambiarra; removê-lo simplifica o código.

Bug 4 — createSession() nunca chamado:
Esse era o mais crítico. O comentário no código (BUG-FIX: Prior to this fix...) explica bem a cadeia de falha. A lógica loadSession() → null → createSession() é correta.

Bug 5 — path do runner do PreCompact:
Adicionar node_modules/aios-core/ ao path resolve o problema de resolução de módulo sem hardcodar caminhos absolutos.

Bug 6 + timeout:
Usar caminhos relativos em vez de $CLAUDE_PROJECT_DIR e remover timeout: 10 são decisões acertadas. O Claude Code gerencia timeouts nativamente (60s default).

Testes:
Os 4 testes atualizados em artifact-copy-pipeline.test.js refletem corretamente o novo comportamento (timeout indefinido). Cobertura adequada para o escopo.

Único ponto de observação:
O cleanOrphanTmpFiles() usa .includes('.tmp.') — o padrão é seguro para o caso atual, mas se algum arquivo legítimo tiver .tmp. no nome por outra razão, seria deletado silenciosamente. Considerar .endsWith('.tmp') ou um padrão regex mais específico em iteração futura.

PR bem estruturado, bugs bem documentados e correções pontuais sem over-engineering. LGTM.

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.

2 participants