How to Debug Hook Failures Using Log Files in $TMPDIR/letta-claude-sync-$UID

When Claude Code reports a "hook error" in Claude Subconscious, examine the per-user log directory at $TMPDIR/letta-claude-sync-$UID/ where each hook writes to dedicated files (e.g., session_start.log, send_messages.log) containing the exact error details obscured by the main interface.

The letta-ai/claude-subconscious repository enhances Claude Code with four background TypeScript hooks—SessionStart, UserPromptSubmit, PreToolUse, and Stop—that execute as Node.js scripts from the scripts/ directory. Because these processes run asynchronously outside the main Claude Code interface, their diagnostic output streams to temporary log files rather than the terminal. Learning to debug hook failures using log files in $TMPDIR/letta-claude-sync-$UID/ enables you to resolve API authentication errors, state corruption, and runtime exceptions that cause silent failures.

Understanding the Log Directory Structure

How the Path is Constructed

The temporary directory path is built dynamically in scripts/conversation_utils.ts by the getTempStateDir() function at lines 60-63【/cache/repos/github.com/letta-ai/claude-subconscious/main/scripts/conversation_utils.ts#L60-L63】. This utility combines:

  • os.tmpdir(): Resolves to the system temporary directory (typically /tmp on macOS and Linux)
  • process.getuid() or process.pid: Appends the Unix user ID, falling back to the process ID if getuid is unavailable

The resulting path follows the pattern $TMPDIR/letta-claude-sync-$UID/, ensuring per-user isolation of log data.

Log Files by Hook Type

Each hook script creates and writes to a specific log file within this directory:

  • SessionStart (scripts/session_start.ts): Writes diagnostic messages to session_start.log via the log() function after resolving the path through getTempStateDir() at lines 71-79【/cache/repos/github.com/letta-ai/claude-subconscious/main/scripts/session_start.ts#L71-L79】
  • Stop (scripts/send_messages_to_letta.ts): Defines LOG_FILE at lines 48-51 to create send_messages.log for capturing shutdown synchronization events【/cache/repos/github.com/letta-ai/claude-subconscious/main/scripts/send_messages_to_letta.ts#L48-L51】
  • PreToolUse (scripts/pretool_sync.ts): Outputs execution details to pretool_sync.log using the identical logging pattern
  • UserPromptSubmit (scripts/sync_letta_memory.ts): While lacking an explicit file logger, this hook prints debug messages to stderr when LETTA_DEBUG=1 is enabled; Claude Code captures this stream into the same temporary directory

Locating Your Log Files Before Debugging

Determine the exact log directory for your session using environment variables and the id command:


# Resolve the complete log directory path

LOG_DIR="${TMPDIR:-/tmp}/letta-claude-sync-$(id -u)"
echo "$LOG_DIR"

# List available log files

ls -la "$LOG_DIR"

If the directory does not exist, the hooks have not yet initialized or lack permissions to write to the temporary filesystem.

Debugging Hook Failures Step by Step

1. Inspect the Relevant Log File

Match the failing hook to its log file and examine the tail for recent errors:


# For SessionStart failures

tail -n 40 "${TMPDIR:-/tmp}/letta-claude-sync-$(id -u)/session_start.log"

# For Stop hook failures

tail -n 40 "${TMPDIR:-/tmp}/letta-claude-sync-$(id -u)/send_messages.log"

2. Enable Verbose Debug Output

Set the LETTA_DEBUG environment variable before starting Claude Code to force the memory-sync hook (sync_letta_memory.ts) to emit detailed diagnostics:

export LETTA_DEBUG=1
export LETTA_MODE=whisper  # or full/off as required

# Launch Claude Code normally

Debug messages now stream to stderr and are captured in the temporary directory alongside other hook logs.

3. Watch Logs in Real-Time

Reproduce the failure while monitoring the log file to capture transient errors:

tail -f "${TMPDIR:-/tmp}/letta-claude-sync-$(id -u)/session_start.log"

4. Interpret Common Error Patterns

Search for these specific strings to identify root causes:

  • Creating new conversation… — The hook is successfully contacting the Letta API
  • Error: Letta API error (401)… — Authentication failure; verify LETTA_API_KEY is exported correctly
  • Failed to load state… — Corrupted or permission-denied state file in .letta/claude/
  • Mode: off — Intentional exit; the hook disabled itself because LETTA_MODE=off is set

Advanced Debugging Code Examples

Extract the conversation URL from SessionStart logs to verify API connectivity:

LOG_DIR="${TMPDIR:-/tmp}/letta-claude-sync-$(id -u)"
tail -n 20 "$LOG_DIR/session_start.log" | grep -i conversation

Force a fresh conversation when the state map becomes stale by removing the specific session entry:

SESSION_ID=$(jq -r '.session_id' < "$HOME/.claude/session.json")
LOG_FILE="${TMPDIR:-/tmp}/letta-claude-sync-$(id -u)/session_start.log"
sed -i.bak "/$SESSION_ID/d" "$LOG_FILE"

Stream debug output from the memory synchronization hook continuously:

export LETTA_DEBUG=1
tail -f "${TMPDIR:-/tmp}/letta-claude-sync-$(id -u)/sync_letta_memory.log"

Summary

  • Hook logs reside in $TMPDIR/letta-claude-sync-$UID/ as defined in scripts/conversation_utils.ts via getTempStateDir()
  • Four dedicated log files exist: session_start.log, send_messages.log, pretool_sync.log, and sync_letta_memory.log (stderr capture)
  • Enable debug mode by setting LETTA_DEBUG=1 to expose detailed memory-sync diagnostics
  • Common fixes involve checking LETTA_API_KEY for 401 errors, verifying directory permissions, and clearing stale state entries
  • Real-time monitoring with tail -f captures errors as they occur during hook execution

Frequently Asked Questions

What does the $UID suffix represent in the log directory path?

The $UID suffix represents the Unix user ID retrieved via process.getuid(), or the process ID if the function is unavailable, as implemented in scripts/conversation_utils.ts【/cache/repos/github.com/letta-ai/claude-subconscious/main/scripts/conversation_utils.ts#L60-L63】. This ensures separate log directories for each system user when multiple accounts run Claude Code on the same machine.

Why are my log files empty after a hook failure?

Empty logs indicate either the hook process crashed before initializing the logger, Claude Code sandboxing prevented file system access (common on macOS with strict privacy settings), or the temporary directory is not writable. Verify permissions with ls -ld "$TMPDIR" and ensure the letta-claude-sync-$UID directory is owned by your user.

How do I troubleshoot "Letta API error (401)" messages in the logs?

A 401 error signifies authentication failure against the Letta API. Verify that the LETTA_API_KEY environment variable is exported in your shell profile and accessible to the Claude Code process. The session_start.log file captures the full error response from the API, which often includes additional context about missing or invalid credentials.

Which log file corresponds to the memory synchronization hook?

The UserPromptSubmit hook (memory synchronization) writes to sync_letta_memory.log only when LETTA_DEBUG=1 is enabled, as the scripts/sync_letta_memory.ts file outputs to stderr rather than a dedicated file logger. Without debug mode enabled, this hook produces minimal output, so set the environment variable before starting Claude Code to capture diagnostic data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →