Logging

Braide uses structured JSONL logging across all server-side code. Every log entry is a single JSON object written to stdout and to log files on disk. Console and file outputs have independent log levels, so you can keep the terminal clean while capturing full detail on disk.

Log Levels

Log levels control the verbosity of output. Console and file destinations each have their own level — a message is emitted to a destination only if its level meets or exceeds that destination's minimum.

LevelDefault forDescription
traceHigh-frequency streaming data. Individual agent message and thought chunks with full event body. Produces significant volume during active sessions.
debugFileRequest/response detail, protocol messages, and internal state. Includes API request parameters, ACP protocol negotiation, session config options, heartbeat tick summaries, and terminal process signals.
infoConsoleSignificant state changes and operational events. Agent start/stop/ready, session creation, worktree operations, scheduled prompt triggers, terminal connect/disconnect, shutdown, and archive pruning.
warningDegraded but recoverable conditions. Skipped scheduled sessions (missing acpId or agent not running), loadSession failures falling back to newSession.
errorOperation failures. Schema decode errors, worktree/branch failures, agent process crashes, ACP connection drops, file I/O errors, GitHub API failures.
fatalUnrecoverable process errors. Uncaught exceptions and unhandled promise rejections that trigger process shutdown.
noneTestsSuppress all output. Used as the default in test runs so log lines don't pollute test reporter output; override with ACP_LOG_LEVEL=info npm test to see logs while debugging.

Each level includes all levels above it. Setting the console level to debug shows debug, info, warning, error, and fatal messages in the terminal.

Log Areas

Every log entry includes an area field identifying the subsystem that produced it. Filter areas using the ACP_LOG_AREAS environment variable.

AreaSubsystemExample messages
storeSession/project data persistenceSchema decode failures for projects, sessions, events, config, schedules
sessionsSession data models(reserved for future use)
lifecycleApp lifecycle and session orchestrationShutdown, agent restore, ACP session create/prompt, archive pruning
clientACP client callbacksSession updates from agent (with update type and event body), file read/write, terminal create/exit
promptPrompt construction(reserved for future use)
agentAgent process managementBinary download, process spawn/exit/restart, ACP protocol init, loadSession/recreateSession
heartbeatScheduled task runnerHeartbeat ticks, pending schedule details, session triggers, prompt completion
terminal-wsTerminal WebSocket serverClient connect/disconnect, spawn failures, message errors, server listen
terminal-managerTerminal process lifecycleProcess group signals, kill operations
worktreeGit worktree operationsWorktree create/remove, branch rename/archive, diff generation, branch listing, merge
eventsEvent file parsingEvent decode failures
trajectoryTrajectory file parsingTrajectory entry decode failures
sseServer-sent eventsSSE replay timing
settingsSettings file I/OSettings load/save failures
permissionPermission handlingPermission requests, safe-kind auto-approvals, user-resolved outcomes
apiAPI route handlersRequest/response logging for sessions, prompt, config, stream, and issue endpoints

Area Filtering

Set ACP_LOG_AREAS to a comma-separated list of areas to include. Use - prefix to exclude. Default is * (all areas).

# Only show heartbeat and agent logs
ACP_LOG_AREAS=heartbeat,agent

# Show everything except high-volume client updates
ACP_LOG_AREAS=-client

# Show only errors from all areas (combine with level)
ACP_LOG_LEVEL=error

Output Format

Every log entry is a single-line JSON object (JSONL):

{"timestamp":"2026-04-14T09:37:19.941Z","level":"INFO","area":"agent","message":"Agent ready","agentId":"claude-code","name":"Claude Code","version":"1.0.0"}

Fields

FieldAlways presentDescription
timestampYesISO 8601 timestamp
levelYesLog level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
areaYesSubsystem that produced the entry
correlationIdWhen setApp-level session ID for correlating all log entries within a session
messageYesHuman-readable description
acpIdWhen applicableACP SDK session ID (differs from the app session ID)
projectIdWhen applicableProject identifier
errorOn errorsError message string
dataOn session updatesFull event body from the agent

Additional context-specific fields (e.g. agentId, sessionId, path, stopReason) are included as top-level properties.

Correlation IDs

The correlationId field ties together all log entries for a single session across API routes, lifecycle operations, client callbacks, and permission handling. The value is the app-level session ID (e.g. 20260414-103452-zZY76b), not the ACP SDK's internal session UUID.

Use it to filter logs for a specific session:

grep '"correlationId":"20260414-103452-zZY76b"' ~/.braide/logs/acp.log

Redaction

All log entries are automatically scanned for confidential information before being written to any output. Redaction happens at the serialization boundary — both the Effect-TS logger and the imperative logger apply it, so no sensitive data reaches stdout or log files regardless of log level.

Detected values are replaced with [REDACTED]. The original data is never written.

Key-Name Detection

Any annotation key matching a sensitive pattern is redacted. The following patterns trigger redaction (case-insensitive):

token, secret, password, passwd, pwd, key, apikey, api_key, auth, credential, bearer, cookie, session, jwt, private, access_token, refresh_token, client_secret, encryption

Structural keys used by the logging framework itself (correlationId, projectId, sessionId, area, level, timestamp, message, statusCode, method, path, url, duration, count, id, etc.) are safe-listed and never redacted.

Value-Pattern Detection

String values are checked against known secret formats regardless of key name. This catches secrets that end up in generically-named fields. Detected patterns include:

PatternExample
GitHub tokensghp_*, gho_*, ghu_*, ghs_*, github_pat_*
OpenAI-style API keyssk-*, sk-proj-*
JWTseyJhbG... (three dot-separated base64url segments)
Bearer / Basic auth headersBearer <token>, Basic <encoded>
Slack tokensxoxb-*, xoxp-*, xoxa-*, xoxr-*, xoxs-*
AWS access key IDsAKIA* (20 uppercase alphanumeric characters)
PEM private keys-----BEGIN PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY-----

Nested Data

Redaction walks nested objects and arrays up to 8 levels deep. A secret buried inside an annotation object like { config: { credentials: { token: "ghp_..." } } } is redacted at the leaf.

Example

Given a log call:

log.info("auth check", { userId: "u-123", token: "ghp_abc123...", status: "ok" })

The written log entry will be:

{"timestamp":"...","level":"INFO","area":"api","message":"auth check","userId":"u-123","token":"[REDACTED]","status":"ok"}

Log File Locations

When BRAIDE_HOME is set (or defaults to ~/.braide), logs are written to disk in addition to stdout. Different files exist to keep different failure modes recoverable — a crash that prevents the async logger from flushing still leaves on-disk evidence elsewhere.

Structured Logs

Global Log (Next.js server)

~/.braide/logs/acp.log

Contains all log entries emitted by the Braide Next.js server — session activity, agents, ACP protocol traffic, scheduled prompts, archive pruning, etc. Rotates at 5 MB, keeping 5 rotated files (.1 through .5).

In the packaged Electron desktop app, the main process also appends its own operational events here — update checks (manual and automatic), server fork lifecycle, port allocation, "server ready" / "failed to start", CSP setup, and IPC events — so the in-app Log Viewer (which reads acp.log) surfaces them alongside the server's entries. Both processes write acp.log concurrently; the rotating sink is inode-aware, so each writer reopens onto the canonical path when the other rotates. The main process does not re-persist the Next.js child's forwarded stdout/stderr here — those lines are already written to acp.log natively by the child's own logger, so re-writing them would duplicate every line.

Electron Main Log (desktop app only)

~/.braide/logs/electron-main.log

Present only when running the packaged Electron desktop app. Scoped to the main process's crash/exit evidence — the silent-crash safety net: the Next.js child's exit record (code, signal, uptime, and the last 50 stderr lines if it dies), renderer/utility/GPU process-gone events, and the hung-shutdown SIGKILL warning. Routine operational events go to acp.log (above), and the child's forwarded stdout/stderr is not persisted here at all — keeping this file small and focused on "what died". Rotates the same way as acp.log (5 MB × 5 files).

Session Logs

~/.braide/projects/{projectId}/sessions/{sessionId}/logs/session.log

Contains only log entries that include both projectId and sessionId annotations. These files are append-only with no rotation, preserving the complete log history for each session.

Crash Logs (synchronous fallback)

The structured logger uses batched, async writes — fast under normal load but unable to guarantee that the final entries reach disk during an abrupt exit (OOM, SIGABRT, native crash). Two synchronous-only files capture those last moments using direct fs.appendFileSync calls inside the uncaughtException / unhandledRejection handlers:

~/.braide/logs/electron-main-crash.log     # Electron main process
~/.braide/logs/braide-crash.log            # Next.js child process

Each entry is a single JSON line with timestamp, level: "FATAL", source: "electron" | "braide", kind: "uncaughtException" | "unhandledRejection", message, and stack. These files are append-only and uncapped — manual rotation is fine because volume should be very low (one entry per crash). When troubleshooting a silent freeze or unexplained exit, check these first — they are the only files guaranteed to receive the final FATAL record.

Cross-Process Crash Trail

~/.braide/logs/crashes.log

A sticky, capped-append log shared by both processes. Holds the last 200 lifecycle / FATAL entries with no rotation, so a long-running session cannot lose its earlier crashes to the 5 MB acp.log rotation budget. Receives:

  • All FATAL-level entries from electronLogger (Electron main) and from the Effect / imperative loggers (Next.js child).
  • All entries tagged area: "lifecycle" from the Next.js child — startup, shutdown phases, signal handling, agent restoration, etc.

This is the right file for "show me the last few weeks of crashes across sessions" triage. When 200 entries is exceeded, the file is rewritten in place to keep the most recent 200.

Configuration

Log levels can be configured from the Settings UI, environment variables, or the settings file. Changes made through the Settings UI or settings file take immediate effect without restarting the server.

Settings UI

Open Settings > System > Logging to configure log levels. The panel shows two independent level pickers:

  • Console Log Level — controls what appears in the terminal (default: info)
  • File Log Level — controls what is written to log files on disk (default: debug)

Changes take effect immediately when a level is selected.

Log Viewer

The View Log button on the File Log Level card opens a near-full-screen log viewer for browsing the global log file.

Layout: A table with columns for Timestamp, Level, Area, Correlation ID, Message, and Update Type. Columns size to fit their content; the Message column takes the remaining space. The header row and filter row stay pinned when scrolling.

Scrolling and lazy loading: The log displays in ascending chronological order (oldest at top, newest at bottom). The most recent 100 entries load initially, with the view scrolled to the bottom. Scrolling up lazy-loads older entries in batches. "No earlier logs." appears at the top when all history is loaded; "Waiting for logs..." appears at the bottom.

Live tailing: While the viewer is open, new log entries are polled every 2 seconds and appended at the bottom. The view auto-scrolls to follow new entries only when already scrolled to the bottom. If you have scrolled up to browse history, new entries accumulate silently. The Skip to Current button reloads the latest entries and scrolls to the bottom.

Detail expansion: Rows with additional data beyond the standard columns show a small arrow (▶) next to the timestamp. Click the row to expand an inline panel showing all extra fields as prettified JSON. A Copy to clipboard button in the top-right corner of the detail panel copies the JSON to the clipboard. Use Arrow Up / Arrow Down to move the detail panel between expandable rows without clicking. Escape closes the detail panel; pressing Escape again closes the viewer.

Column Filters

A row of filter controls sits directly below the column headers. All filters are cumulative — only rows matching every active filter are shown. The Clear Filters button in the header resets all filters at once.

FilterTypeBehavior
LevelMulti-select dropdownSelect one or more log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL). Options are populated from the loaded entries. Shows "All" when nothing is selected.
AreaMulti-select dropdownSelect one or more log areas (e.g. agent, lifecycle, store). Options are populated from the loaded entries.
Correlation IDSingle-value pillClick any correlation ID in the table to filter to that session's entries. The active filter appears as a pill with a × button to clear it. Only one correlation ID can be active at a time.
MessageText inputCase-insensitive substring search across log messages. Filters in real time as you type.
Update TypeMulti-select dropdownSelect one or more update types. Options are populated from the loaded entries.

Filtering is client-side against already-loaded lines — lazy loading and live tailing continue in the background so filtered results grow as more data loads. The status bar in the footer shows how many entries are loaded and the total count in the file.

Level Colors

Log levels are color-coded for quick scanning:

LevelColor
TRACEMuted gray
DEBUGSecondary text
INFOAccent blue
WARNGolden (#e6a817)
ERRORRed (#e53935)
FATALDark red (#b71c1c)

Environment Variables

VariableDefaultDescription
ACP_LOG_LEVELinfoMinimum level for console (stdout) output
ACP_LOG_FILE_LEVELdebugMinimum level for file output (global + session logs)
ACP_LOG_AREAS*Comma-separated area filter. Use - prefix to exclude (e.g. -client,-sse)

Settings File

Log settings can also be persisted in ~/.braide/settings.json under the logging key:

{
  "logging": {
    "consoleLevel": "info",
    "fileLevel": "debug",
    "areas": "agent,lifecycle,heartbeat"
  }
}

Persisted settings are applied to the environment at startup and whenever changed via the Settings UI. Environment variables set externally take precedence over persisted settings.