Skip to content
Development
Hook

Hooks

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

From plugin
claude-subconscious
2.9k4 hooks
Install
> /plugin marketplace add letta-ai/claude-subconscious
> /plugin install claude-subconscious@claude-subconscious

Ships with claude-subconscious. 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.

  • Matches*node "${CLAUDE_PLUGIN_ROOT:-.}/hooks/silent-npx.cjs" tsx "${CLAUDE_PLUGIN_ROOT:-.}/scripts/session_start.ts"node "${CLAUDE_PLUGIN_ROOT:-.}/hooks/silent-npx.cjs" tsx "${CLAUDE_PLUGIN_ROOT:-.}/scripts/sync_letta_memory.ts"

PreToolUse

  • Matches*node "${CLAUDE_PLUGIN_ROOT:-.}/hooks/silent-npx.cjs" tsx "${CLAUDE_PLUGIN_ROOT:-.}/scripts/pretool_sync.ts"

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.

  • Matches*node "${CLAUDE_PLUGIN_ROOT:-.}/hooks/silent-npx.cjs" tsx "${CLAUDE_PLUGIN_ROOT:-.}/scripts/sync_letta_memory.ts"

Stop

  • Matches*node "${CLAUDE_PLUGIN_ROOT:-.}/hooks/silent-npx.cjs" tsx "${CLAUDE_PLUGIN_ROOT:-.}/scripts/send_messages_to_letta.ts"
Read hooks/hooks.json

Where it lives

  • hooks/build.ps1GitHub
    Read the script
    # Build silent-launcher.exe from SilentLauncher.cs
    # Requires .NET Framework csc.exe (ships with Windows)
    #
    # Usage: powershell -ExecutionPolicy Bypass -File hooks/build.ps1
    
    $ErrorActionPreference = 'Stop'
    $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
    $cs = Join-Path $scriptDir 'SilentLauncher.cs'
    $exe = Join-Path $scriptDir 'silent-launcher.exe'
    
    # Find csc.exe from the .NET Framework directory
    $csc = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319\csc.exe'
    if (-not (Test-Path $csc)) {
        $csc = Join-Path $env:WINDIR 'Microsoft.NET\Framework\v4.0.30319\csc.exe'
    }
    if (-not (Test-Path $csc)) {
        Write-Error "csc.exe not found. Ensure .NET Framework 4.x is installed."
        exit 1
    }
    
    Write-Host "Building silent-launcher.exe ..."
    & $csc /nologo /out:$exe /platform:anycpu /target:winexe $cs
    
    if ($LASTEXITCODE -eq 0) {
        Write-Host "Built: $exe"
    } else {
        Write-Error "Build failed (exit code $LASTEXITCODE)"
        exit $LASTEXITCODE
    }
    
  • hooks/silent-npx.cjsRunsGitHub
    Read the script
    #!/usr/bin/env node
    /**
     * Cross-platform launcher for Claude Subconscious hooks.
     *
     * On Windows: delegates to silent-launcher.exe which creates a headless
     * PseudoConsole (ConPTY) + CREATE_NO_WINDOW to eliminate console window
     * flashes on Windows 11 / Windows Terminal.
     *
     * On other platforms: runs tsx directly via node — no console issue.
     *
     * Called from hooks.json as:
     *   node hooks/silent-npx.cjs tsx scripts/<script>.ts
     */
    const { spawn } = require('child_process');
    const path = require('path');
    const fs = require('fs');
    
    const isWindows = process.platform === 'win32';
    const args = process.argv.slice(2); // e.g. ['tsx', 'path/to/script.ts']
    
    let child;
    
    if (args[0] === 'tsx') {
      let scriptArgs = args.slice(1); // everything after 'tsx'
      const pluginRoot = path.resolve(__dirname, '..');
    
      // Fix for #34: If CLAUDE_PLUGIN_ROOT was empty, script paths resolve to
      // absolute paths like "/scripts/foo.ts" which don't exist. Re-resolve
      // them relative to the plugin root (which we know from __dirname).
      scriptArgs = scriptArgs.map(arg => {
        if (!fs.existsSync(arg) && arg.includes('/scripts/')) {
          const basename = path.basename(arg);
          const resolved = path.join(pluginRoot, 'scripts', basename);
          if (fs.existsSync(resolved)) return resolved;
        }
        return arg;
      });
      const tsxCli = path.join(pluginRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs');
    
      if (isWindows) {
        const silentLauncher = path.join(__dirname, 'silent-launcher.exe');
    
        if (fs.existsSync(silentLauncher) && fs.existsSync(tsxCli)) {
          // PseudoConsole + CREATE_NO_WINDOW: popup-free execution
          child = spawn(silentLauncher, ['node', tsxCli, ...scriptArgs], {
            stdio: 'inherit',
            windowsHide: true,
          });
        } else if (fs.existsSync(tsxCli)) {
          // Fallback: run tsx CLI directly (may flash on Windows Terminal)
          child = spawn(process.execPath, [tsxCli, ...scriptArgs], {
            stdio: 'inherit',
            windowsHide: true,
          });
        } else {
          // Last resort: npx through shell
          child = spawn('npx', args, {
            stdio: 'inherit',
            shell: true,
            windowsHide: true,
          });
        }
      } else {
        // Non-Windows: no console window issues
        if (fs.existsSync(tsxCli)) {
          child = spawn(process.execPath, [tsxCli, ...scriptArgs], {
            stdio: 'inherit',
          });
        } else {
          child = spawn('npx', args, {
            stdio: 'inherit',
          });
        }
      }
    } else {
      // Non-tsx command: use npx
      child = spawn('npx', args, {
        stdio: 'inherit',
        shell: isWindows,
        windowsHide: isWindows,
      });
    }
    
    child.on('exit', (code) => {
      process.exit(code || 0);
    });
    
    child.on('error', (err) => {
      console.error('Failed to start subprocess:', err);
      process.exit(1);
    });
    
  • hooks/stdio-preload.cjsGitHub
    Read the script
    // Preload: delivers stdin from temp file via unshift, captures stdout to temp file.
    // Loaded via --require in the node command line.
    // Used with PseudoConsole + CREATE_NO_WINDOW where pipe I/O is not available.
    'use strict';
    
    const fs = require('fs');
    const stdoutFile = process.env.SL_STDOUT_FILE;
    const stdinFile = process.env.SL_STDIN_FILE;
    
    // --- STDIN: Read from temp file, unshift onto existing Socket ---
    if (stdinFile) {
      try {
        const data = fs.readFileSync(stdinFile);
        if (data.length > 0) {
          const sock = process.stdin;
          sock.pause();
          sock.unshift(data);
          process.nextTick(() => sock.push(null));
        }
      } catch (e) { /* stdin file may not exist */ }
    }
    
    // --- STDOUT/STDERR: Capture all writes to temp file ---
    if (stdoutFile) {
      try {
        const fd = fs.openSync(stdoutFile, 'a');
    
        const origWrite = process.stdout.write.bind(process.stdout);
        process.stdout.write = function(chunk, encoding, callback) {
          try {
            const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, typeof encoding === 'string' ? encoding : 'utf8');
            fs.writeSync(fd, buf);
          } catch (e) { /* ignore write errors */ }
          return origWrite(chunk, encoding, callback);
        };
    
        const origErrWrite = process.stderr.write.bind(process.stderr);
        process.stderr.write = function(chunk, encoding, callback) {
          try {
            const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, typeof encoding === 'string' ? encoding : 'utf8');
            fs.writeSync(fd, buf);
          } catch (e) { /* ignore write errors */ }
          return origErrWrite(chunk, encoding, callback);
        };
    
        process.on('exit', () => {
          try { fs.closeSync(fd); } catch(e) {}
        });
      } catch (e) { /* stdout setup error */ }
    }
    

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 withclaude-subconscious

A background agent that whispers to Claude Code. A subconcious agent that watches your sessions, reads your files, builds up memory over time, and whispers guidance back.

Get the whole plugin
Stats
2,868
Stars
213
Forks
Maintained
Maintenance
TypeScript
Language
MIT
License
1mo ago
Last commit
6mo ago
Created

Repo: letta-ai/claude-subconscious