- Nx 22.7 monorepo (pnpm 11.1, TypeScript 5.9, Node 24) - apps/api: NestJS 11 (CJS conforme CODING-RULES.md PGD-DB-004) - apps/web: React 19 + Vite 8 (ESM) - libs/shared/api-interface: Zod contract base - Docker Compose dev: Postgres 18, Valkey 8, MinIO, Mailpit - WDS artifacts: - design-artifacts/A-Product-Brief/ (5 docs canônicos + 16 dialogs) - design-artifacts/B-Trigger-Map/ (hub + 4 personas + feature impact) - Stack canon: STACK.md v2.2 + CODING-RULES.md v2.0 + brand.md - AGENTS.md + README.md como entrada para devs/agentes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7.2 KiB
Stop Hook Configuration
This document defines the Stop hook required for the story-automator to prevent premature stopping during orchestration in Claude or Codex.
Related: See stop-hook-troubleshooting.md for child session handling, manual override, and troubleshooting.
Overview
The Stop hook uses a marker file approach:
- When story-automator starts → Creates marker file with orchestration context
- When the active agent tries to stop → Hook script checks marker file
- If no marker or completed → Allow stop (normal agent usage)
- If marker exists with pending stories → Block stop with continuation guidance
- When story-automator completes → Removes marker file
Important (v2 fix): The hook intentionally does NOT check the stop_hook_active flag. This flag stays true for the entire session after one blocked stop, which caused premature exits in long orchestrations. The marker file alone is the source of truth.
Multi-Project Support (v2.0)
CRITICAL: The marker file is now PROJECT-SCOPED to support running story-automator on multiple projects simultaneously.
Old location (DEPRECATED): /tmp/.story-automator-active
New location: runtime-specific project marker resolved by orchestrator-helper marker path
Why Project-Scoped?
When running story-automator on multiple projects at the same time:
- Old: All projects shared
/tmp/.story-automator-active→ Cross-project interference - New: Each project has its own marker in the active runtime layout. The marker follows the active installed skill root parent, for example
.claude/,.agents/, or.codex/.
How It Works
- The installed hook command exports
PROJECT_ROOTfor the target project before invokingstory-automator stop-hook - The stop hook resolves the marker from
PROJECT_ROOT, not from the caller's ambient working directory - Project A's stop hook only sees Project A's marker
- Project B's stop hook only sees Project B's marker
Do not hard-code the marker path. Use orchestrator-helper marker path; this keeps Claude, .agents-based Codex, and .codex-based Codex installs consistent with the active skill root.
State Files Also Scoped
The status check script state files are also project-scoped:
- Old:
/tmp/.tmux-session-{SESSION}-state.json - New:
/tmp/.sa-{project_hash}-session-{SESSION}-state.json
Where project_hash = first 8 chars of MD5 hash of project root path.
Hook Configuration
Runtime Selection
The helper selects hook configuration syntax from the active provider:
BMAD_RUNTIME_PROVIDERSTORY_AUTOMATOR_RUNTIME_PROVIDER
Set one of these to claude or codex to force the provider. If none is set, the helper infers the provider from the installed skill root.
AI_AGENT only selects child-agent runtime for spawned work. It does not decide which top-level hook files are written.
The provider decides which hook files are written. Marker location is resolved separately and follows the active installed story-automator skill root when possible. For example, a Codex run using a migrated .claude/skills/bmad-story-automator install still uses the .claude/.story-automator-active marker so the hook and orchestrator read the same file.
For Claude, add this to the target project's .claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/absolute/path/to/scripts/story-automator stop-hook",
"timeout": 10
}
]
}
]
}
}
For Codex, enable hooks in the target project's .codex/config.toml:
[features]
codex_hooks = true
Then add this to .codex/hooks.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/absolute/path/to/scripts/story-automator stop-hook",
"timeout": 10,
"statusMessage": "Checking story automator state"
}
]
}
]
}
}
Codex trust is separate from hook configuration. A project can have the Story Automator hook written to disk and still require trust approval before Codex will run it. ensure-stop-hook now reports that state as pending trust instead of verified.
Binary Path is Always Absolute
The stop hook binary resolves itself to an absolute path. Regardless of how the caller passes the --command argument (relative, project-relative, or absolute), the helper stores a consistent absolute path in .claude/settings.json or .codex/hooks.json.
This prevents the inconsistency where the AI agent resolves frontmatter paths differently across sessions, which previously caused repeated hook installations and unnecessary restart loops.
Migration: If an existing hook config contains a relative or project-relative path, ensure-stop-hook will normalize it to absolute in-place without triggering a restart (reason: "hook_normalized").
When hook fails with "no such file or directory":
- Verify BMAD is installed in the target project
- Check the binary exists in the active runtime skills tree, for example:
test -x <installed-skill-root>/bmad-story-automator/scripts/story-automator - Ensure binary is executable:
chmod +x <installed-skill-root>/bmad-story-automator/scripts/story-automator
Marker File Format
Location (v2.0): resolved by orchestrator-helper marker path
Note: The orchestrator adds the active marker entry returned by orchestrator-helper marker path to .gitignore. Common entries are .claude/.story-automator-active, .agents/.story-automator-active, and .codex/.story-automator-active.
Content (JSON - v1.2.0 with heartbeat):
{
"epic": "epic-01",
"currentStory": "story-01",
"storiesRemaining": 3,
"stateFile": "/path/to/orchestration-epic01.md",
"startedAt": "2026-01-13T10:00:00Z",
"heartbeat": "2026-01-13T10:30:00Z",
"pid": 12345
}
Fields (v1.2.0):
heartbeat: Last activity timestamp, updated periodically during executionpid: Process ID of the orchestrator (helps detect crashed sessions)
Staleness Check
The stop hook checks if marker heartbeat is older than 30 minutes (stale = orchestrator crashed). If stale, allow stop. See story-automator stop-hook for implementation.
Verification Logic
The orchestrator verifies hook installation at startup:
1. Resolve active runtime provider
2. For Claude, check `.claude/settings.json`; for Codex, check `.codex/hooks.json` and `.codex/config.toml`
3. Parse hook JSON and look for hooks.Stop array
4. Check if any hook command contains "story-automator stop-hook"
IF found → Continue
IF not found → Add hook, instruct restart
Hook Behavior
| Scenario | Action |
|---|---|
STORY_AUTOMATOR_CHILD=true |
exit 0 → Always allow (child session) |
| No marker file | exit 0 → Allow stop |
Marker exists, storiesRemaining=0 |
exit 0 → Allow stop |
Marker exists, storiesRemaining > 0 |
Output JSON → Block stop with reason |
Key fix (Session 10): The hook no longer checks stop_hook_active. This flag was causing premature exits in long orchestrations because it stays true for the entire session after the first blocked stop.