Skip to content
Testing
Hook

Hooks

What bkit runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.

From plugin
bkit
58944 skills34 agents2 commands21 hooks
Install
> /plugin marketplace add popup-studio-ai/bkit-claude-code

Ships 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

  • MatchesWrite|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"
  • MatchesBashnode "${CLAUDE_PLUGIN_ROOT}/scripts/unified-bash-pre.js"

PostToolUse

  • MatchesWrite|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"
  • MatchesBashnode "${CLAUDE_PLUGIN_ROOT}/scripts/unified-bash-post.js"
  • MatchesSkillnode "${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

  • Matchesauto|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

  • MatchesBash|Write|Editnode "${CLAUDE_PLUGIN_ROOT}/scripts/tool-failure-handler.js"

InstructionsLoaded

  • node "${CLAUDE_PLUGIN_ROOT}/scripts/instructions-loaded-handler.js"

ConfigChange

  • Matchesproject_settings|skillsnode "${CLAUDE_PLUGIN_ROOT}/scripts/config-change-handler.js"

PermissionRequest

  • MatchesWrite|Edit|Bashnode "${CLAUDE_PLUGIN_ROOT}/scripts/permission-request-handler.js"

Notification

  • Matchespermission_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"
Read hooks/hooks.json

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.

Ships withbkit

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.

Get the whole plugin