Hooks
What bkit runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
> /plugin marketplace add popup-studio-ai/bkit-claude-codeShips with bkit. Installing the plugin gets these hooks.
What fires, and when
SessionStart
Fires once when a session begins, and again after a context compaction. It is where a plugin sets up its environment, or restores state the compaction dropped.
node "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.js"
PreToolUse
- Matches
Write|Editnode "${CLAUDE_PLUGIN_ROOT}/scripts/pre-write.js"node "${CLAUDE_PLUGIN_ROOT}/scripts/lint-skill-md.js"node "${CLAUDE_PLUGIN_ROOT}/scripts/lint-skill-md.js" - Matches
Bashnode "${CLAUDE_PLUGIN_ROOT}/scripts/unified-bash-pre.js"
PostToolUse
- Matches
Write|Editnode "${CLAUDE_PLUGIN_ROOT}/scripts/unified-write-post.js"node "${CLAUDE_PLUGIN_ROOT}/scripts/pdca-doc-changed-handler.js"node "${CLAUDE_PLUGIN_ROOT}/scripts/pdca-doc-changed-handler.js" - Matches
Bashnode "${CLAUDE_PLUGIN_ROOT}/scripts/unified-bash-post.js" - Matches
Skillnode "${CLAUDE_PLUGIN_ROOT}/scripts/skill-post.js"
Stop
node "${CLAUDE_PLUGIN_ROOT}/scripts/unified-stop.js"
StopFailure
node "${CLAUDE_PLUGIN_ROOT}/scripts/stop-failure-handler.js"
UserPromptSubmit
Fires before Claude sees each prompt you send. A plugin can use it to inject context, so the same instruction reaches the model every turn instead of only at session start.
node "${CLAUDE_PLUGIN_ROOT}/scripts/user-prompt-handler.js"
UserPromptExpansion
node "${CLAUDE_PLUGIN_ROOT}/scripts/user-prompt-expansion-handler.js"
PreCompact
- Matches
auto|manualnode "${CLAUDE_PLUGIN_ROOT}/scripts/context-compaction.js"
PostCompact
node "${CLAUDE_PLUGIN_ROOT}/scripts/post-compaction.js"
TaskCompleted
node "${CLAUDE_PLUGIN_ROOT}/scripts/pdca-task-completed.js"
SubagentStart
node "${CLAUDE_PLUGIN_ROOT}/scripts/subagent-start-handler.js"
SubagentStop
node "${CLAUDE_PLUGIN_ROOT}/scripts/subagent-stop-handler.js"
TeammateIdle
node "${CLAUDE_PLUGIN_ROOT}/scripts/team-idle-handler.js"
SessionEnd
node "${CLAUDE_PLUGIN_ROOT}/scripts/session-end-handler.js"
PostToolUseFailure
- Matches
Bash|Write|Editnode "${CLAUDE_PLUGIN_ROOT}/scripts/tool-failure-handler.js"
InstructionsLoaded
node "${CLAUDE_PLUGIN_ROOT}/scripts/instructions-loaded-handler.js"
ConfigChange
- Matches
project_settings|skillsnode "${CLAUDE_PLUGIN_ROOT}/scripts/config-change-handler.js"
PermissionRequest
- Matches
Write|Edit|Bashnode "${CLAUDE_PLUGIN_ROOT}/scripts/permission-request-handler.js"
Notification
- Matches
permission_prompt|idle_promptnode "${CLAUDE_PLUGIN_ROOT}/scripts/notification-handler.js"
CwdChanged
node "${CLAUDE_PLUGIN_ROOT}/scripts/cwd-changed-handler.js"
TaskCreated
node "${CLAUDE_PLUGIN_ROOT}/scripts/task-created-handler.js"
In the plugin's words
How bkit describes its own hook set.
bkit Vibecoding Kit v2.1.34 - Claude Code. IMPORTANT: `timeout` is in SECONDS (Claude Code default 600 for command hooks). Values here were 1000x too large through v2.1.33 — a declared 10000 meant 2h46m, not 10s, which is why a hung Stop hook could stall a session for ~15
Where it lives
- hooks/session-start.jsRunsGitHub
Read the script
#!/usr/bin/env node /** * bkit Vibecoding Kit - SessionStart Hook (v2.1.34, uses BKIT_VERSION from lib/core/version) * * Thin orchestrator that delegates to startup modules: * 1. migration - Legacy path migration (docs/ -> .bkit/) * 2. restore - PLUGIN_DATA backup restoration * 3. contextInit - Context Hierarchy, Memory Store, Import Resolver, Fork cleanup, ensureBkitDirs * 4. onboarding - Onboarding message generation, env vars, trigger table * 5. sessionCtx - additionalContext string building for hook output * 6. dashboard - PDCA progress bar rendering (prepended to additionalContext) * 7. workflowMap - v2.0.0 Workflow map rendering (PDCA phase visualization) * 8. controlPanel- v2.0.0 Control panel rendering (automation level display) * 9. staleDetect - v2.0.0 Stale feature detection (lifecycle.js) */ const { BKIT_PLATFORM } = require('../lib/core/platform'); const { debugLog } = require('../lib/core/debug'); const { readStdinSync } = require('../lib/core/io'); /* * v2.1.34: read the hook payload. * * This handler previously ignored stdin entirely, which cost it two things. * * First, `source` — Claude Code reports whether the session began via * `startup`, `resume`, `clear`, `compact` or `fork`. bkit had no way to tell * them apart, and the `once: true` flag it carried in hooks.json to approximate * "only on a fresh session" was never honoured there in the first place (it is * read only from skill frontmatter). Confirmed by resuming a session and * watching this hook fire a second time. * * Second, dispatch observability: reading through the shared reader stamps * `.bkit/runtime/hook-dispatch.ndjson`, which is how the host-integration test * proves from the outside that Claude Code really invokes this hook. A hook * nobody can observe is how eight bkit features stayed dead across releases. * * The reader is bounded (issue #139), so this cannot stall the session. */ const hookPayload = readStdinSync() || {}; const sessionSource = typeof hookPayload.source === 'string' ? hookPayload.source : null; // Log session start debugLog('SessionStart', 'Hook executed', { cwd: process.cwd(), platform: BKIT_PLATFORM, source: sessionSource, }); // --- ENH-148: Defensive cleanup for env vars that should reset on /clear --- // CC /clear resets conversation but env vars persist across sessions. // Clean up bkit-specific runtime env vars to prevent stale state (#37729). const BKIT_RUNTIME_ENV_VARS = [ 'BKIT_PDCA_PHASE', 'BKIT_PRIMARY_FEATURE', 'BKIT_AUTOMATION_LEVEL', 'BKIT_SESSION_ID', 'BKIT_AGENT_ACTIVE', 'BKIT_CHECKPOINT_PENDING', ]; for (const envVar of BKIT_RUNTIME_ENV_VARS) { if (process.env[envVar]) { debugLog('SessionStart', 'Cleaning stale env var', { envVar, value: process.env[envVar] }); delete process.env[envVar]; } } // --- 1. Migration: Legacy path migration --- const migration = require('./startup/migration'); try { migration.run(); } catch (e) { debugLog('SessionStart', 'Migration module failed', { error: e.message }); } // --- 2. Restore: PLUGIN_DATA backup restoration --- const restore = require('./startup/restore'); try { restore.run(); } catch (e) { debugLog('SessionStart', 'Restore module failed', { error: e.message }); } // --- 3. Context Init: Hierarchy, Memory, Imports, Forks --- const contextInit = require('./startup/context-init'); try { contextInit.run(); } catch (e) { debugLog('SessionStart', 'Context init module failed', { error: e.message }); } // --- 4. Onboarding: Messages, env vars, trigger table --- const onboarding = require('./startup/onboarding'); let onboardingContext = { onboardingData: { type: 'new_user', hasExistingWork: false }, triggerTable: '' }; try { onboardingContext = onboarding.run(); } catch (e) { debugLog('SessionStart', 'Onboarding module failed', { error: e.message }); } // --- 5. Session Context: Build additionalContext string --- const sessionContext = require('./startup/session-context'); let additionalContext = ''; try { additionalContext = sessionContext.build(null, onboardingContext); } catch (e) { debugLog('SessionStart', 'Session context module failed', { error: e.message }); } // --- v2.1.1 UI-02: Build dashboard sections in correct display order --- // Order: Session Context → Progress Bar → Workflow Map → Impact View → Agent Panel → Control Panel const dashboardSections = []; // ENH-226 (Issue #77 Phase A): dashboard opt-out gate // When ui.dashboard.enabled=false, skip rendering all 5 boxes // (progress / workflow / impact / agent / control). let _uiDashboardEnabled = true; let _uiDashboardSections = ['progress', 'workflow', 'impact', 'agent', 'control', 'sqm']; try { const { getUIConfig } = require('../lib/core/config'); const _ui = getUIConfig(); if (_ui && _ui.dashboard) { _uiDashboardEnabled = _ui.dashboard.enabled !== false; if (Array.isArray(_ui.dashboard.sections)) _uiDashboardSections = _ui.dashboard.sections; } } catch (_e) { // keep default (true) } // Session Context is already in additionalContext (base content) // It will be placed first in the final output let pdcaStatus = null; let agentState = null; // Load shared state once try { const { getPdcaStatusFull } = require('../lib/pdca/status'); pdcaStatus = getPdcaStatusFull(); } catch (_) {} try { const fs = require('fs'); const agentStatePath = require('path').resolve(process.cwd(), '.bkit/runtime/agent-state.json'); if (fs.existsSync(agentStatePath)) { agentState = JSON.parse(fs.readFileSync(agentStatePath, 'utf-8')); // v2.1.12 Sprint C-1 (#15 fix): reset stale agent-state when last update // is older than `staleFeatureTimeoutDays` (default 7 days from // control-state.json guardrails). Previous behaviour: agent-state kept // referencing a 13-days-idle feature ("cc-version-issue-response") with // an empty sessionId — every new session started by inheriting that // stale lifecycle. We now zero t
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
A Claude Code plugin that verifies AI-generated code against its own design specs. Three commands. Anyone — even someone vibe-coding for the first time — can ship robust, production-quality software.
Repo: popup-studio-ai/bkit-claude-code

