{"record":{"id":"063d9eb4d3777763","repo":"Yeachan-Heo/oh-my-codex","slug":"failed-to-run-notify-hook-result-error-message","errorCode":null,"errorMessage":"failed to run notify-hook: ${result.error.message}","messagePattern":"failed to run notify-hook: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cli/tmux-hook.ts","lineNumber":474,"sourceCode":"  const threadId = `tmux-test-${Date.now()}`;\n  const turnId = `turn-${Date.now()}`;\n  const message = args.join(' ').trim() || 'tmux-hook test payload';\n  const payload = {\n    type: 'agent-turn-complete',\n    cwd,\n    'thread-id': threadId,\n    'turn-id': turnId,\n    'input-messages': ['omx tmux-hook test'],\n    'last-assistant-message': message,\n  };\n\n  const result = spawnSync(process.execPath, [notifyHook, JSON.stringify(payload)], {\n    cwd,\n    encoding: 'utf-8',\n      windowsHide: true,\n    });\n  if (result.error) {\n    throw new Error(`failed to run notify-hook: ${result.error.message}`);\n  }\n  if (result.status !== 0) {\n    throw new Error(`notify-hook exited ${result.status}: ${(result.stderr || result.stdout || '').trim()}`);\n  }\n\n  console.log('tmux-hook test: notify-hook executed.');\n  console.log(`thread_id=${threadId}`);\n  console.log(`turn_id=${turnId}`);\n  console.log('Check: .omx/logs/tmux-hook-YYYY-MM-DD.jsonl for skip/reason codes.');\n}\n","sourceCodeStart":456,"sourceCodeEnd":485,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/tmux-hook.ts#L456-L485","documentation":"During `omx tmux-hook test`, the CLI spawns notify-hook.js with `node <hook> <json-payload>` via spawnSync. This error is the spawn-level failure (result.error), meaning Node could not launch the process at all — typically the executable path is wrong, permissions are denied, or resources are exhausted. It is distinct from error 483, which covers a launched hook that exited non-zero.","triggerScenarios":"spawnSync(process.execPath, [notifyHook, payload]) returns an error object: ENOENT if notifyHook path vanished between the existsSync check and spawn, EACCES if the file lost execute/read permission, or EMFILE/ENOMEM under resource pressure.","commonSituations":"File deleted or moved concurrently (watcher/reinstall mid-test); permission bits stripped by a copy/docker step; CI runners with low file-descriptor limits; antivirus quarantine on Windows.","solutions":["Confirm the file still exists at the printed path and re-run build if not (`npm run build`)","Fix permissions: `chmod +x dist/scripts/notify-hook.js` (and ensure read access)","Check for concurrent processes (file watchers, reinstall jobs) deleting dist/ during the test","On EMFILE/ENOMEM, free file descriptors/memory or raise ulimit and retry"],"exampleFix":"// before\nomx tmux-hook test hello\n// Error: failed to run notify-hook: spawn ENOENT\n\n// after\nls dist/scripts/notify-hook.js || npm run build\nchmod +x dist/scripts/notify-hook.js\nomx tmux-hook test hello","handlingStrategy":"fallback","validationCode":"import { existsSync, accessSync, constants } from 'node:fs';\nfunction canRunHook(hook: string): boolean {\n  try { accessSync(hook, constants.R_OK); return existsSync(hook); } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try { await testTmuxHook(args); } catch (e) { if (e instanceof Error && e.message.startsWith('failed to run notify-hook')) { await rebuildPackage(); await testTmuxHook(args); } else throw e; }","preventionTips":["Rebuild after pulling changes that touch scripts/","Keep dist/ out of clean-on-save watcher tasks","Verify file permissions in dockerized environments"],"tags":["cli","spawn","nodejs","tmux","permissions"],"backgroundTag":"child-process-spawn-failed","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}