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.
> /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"
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.
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.
Repo: letta-ai/claude-subconscious

