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 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.
| Level | Default for | Description |
|---|---|---|
trace | — | High-frequency streaming data. Individual agent message and thought chunks with full event body. Produces significant volume during active sessions. |
debug | File | Request/response detail, protocol messages, and internal state. Includes API request parameters, ACP protocol negotiation, session config options, heartbeat tick summaries, and terminal process signals. |
info | Console | Significant state changes and operational events. Agent start/stop/ready, session creation, worktree operations, scheduled prompt triggers, terminal connect/disconnect, shutdown, and archive pruning. |
warning | — | Degraded but recoverable conditions. Skipped scheduled sessions (missing acpId or agent not running), loadSession failures falling back to newSession. |
error | — | Operation failures. Schema decode errors, worktree/branch failures, agent process crashes, ACP connection drops, file I/O errors, GitHub API failures. |
fatal | — | Unrecoverable process errors. Uncaught exceptions and unhandled promise rejections that trigger process shutdown. |
Each level includes all levels above it. Setting the console level to debug shows debug, info, warning, error, and fatal messages in the terminal.
Every log entry includes an area field identifying the subsystem that produced it. Filter areas using the ACP_LOG_AREAS environment variable.
| Area | Subsystem | Example messages |
|---|---|---|
store | Session/project data persistence | Schema decode failures for projects, sessions, events, config, schedules |
sessions | Session data models | (reserved for future use) |
lifecycle | App lifecycle and session orchestration | Shutdown, agent restore, ACP session create/prompt, archive pruning |
client | ACP client callbacks | Session updates from agent (with update type and event body), file read/write, terminal create/exit |
prompt | Prompt construction | (reserved for future use) |
agent | Agent process management | Binary download, process spawn/exit/restart, ACP protocol init, loadSession/recreateSession |
heartbeat | Scheduled task runner | Heartbeat ticks, pending schedule details, session triggers, prompt completion |
terminal-ws | Terminal WebSocket server | Client connect/disconnect, spawn failures, message errors, server listen |
terminal-manager | Terminal process lifecycle | Process group signals, kill operations |
worktree | Git worktree operations | Worktree create/remove, branch rename/archive, diff generation, branch listing, merge |
events | Event file parsing | Event decode failures |
trajectory | Trajectory file parsing | Trajectory entry decode failures |
sse | Server-sent events | SSE replay timing |
settings | Settings file I/O | Settings load/save failures |
permission | Permission handling | Permission requests, auto-approvals (safe kind, previously approved), persisted approved commands |
api | API route handlers | Request/response logging for sessions, prompt, config, stream, and issue endpoints |
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
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"}
| Field | Always present | Description |
|---|---|---|
timestamp | Yes | ISO 8601 timestamp |
level | Yes | Log level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL) |
area | Yes | Subsystem that produced the entry |
correlationId | When set | App-level session ID for correlating all log entries within a session |
message | Yes | Human-readable description |
acpId | When applicable | ACP SDK session ID (differs from the app session ID) |
projectId | When applicable | Project identifier |
error | On errors | Error message string |
data | On session updates | Full event body from the agent |
Additional context-specific fields (e.g. agentId, sessionId, path, stopReason) are included as top-level properties.
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
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.
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.
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:
| Pattern | Example |
|---|---|
| GitHub tokens | ghp_*, gho_*, ghu_*, ghs_*, github_pat_* |
| OpenAI-style API keys | sk-*, sk-proj-* |
| JWTs | eyJhbG... (three dot-separated base64url segments) |
| Bearer / Basic auth headers | Bearer <token>, Basic <encoded> |
| Slack tokens | xoxb-*, xoxp-*, xoxa-*, xoxr-*, xoxs-* |
| AWS access key IDs | AKIA* (20 uppercase alphanumeric characters) |
| PEM private keys | -----BEGIN PRIVATE KEY-----, -----BEGIN RSA PRIVATE KEY----- |
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.
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"}
When BRAIDE_HOME is set (or defaults to ~/.braide), logs are written to disk in addition to stdout.
~/.braide/logs/acp.log
Contains all log entries from all sessions and subsystems. Rotates at 5 MB, keeping 5 rotated files (.1 through .5).
~/.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.
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.
Open Settings > System > Logging to configure log levels. The panel shows two independent level pickers:
info)debug)Changes take effect immediately when a level is selected.
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.
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.
| Filter | Type | Behavior |
|---|---|---|
| Level | Multi-select dropdown | Select one or more log levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL). Options are populated from the loaded entries. Shows "All" when nothing is selected. |
| Area | Multi-select dropdown | Select one or more log areas (e.g. agent, lifecycle, store). Options are populated from the loaded entries. |
| Correlation ID | Single-value pill | Click 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. |
| Message | Text input | Case-insensitive substring search across log messages. Filters in real time as you type. |
| Update Type | Multi-select dropdown | Select 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.
Log levels are color-coded for quick scanning:
| Level | Color |
|---|---|
| TRACE | Muted gray |
| DEBUG | Secondary text |
| INFO | Accent blue |
| WARN | Golden (#e6a817) |
| ERROR | Red (#e53935) |
| FATAL | Dark red (#b71c1c) |
| Variable | Default | Description |
|---|---|---|
ACP_LOG_LEVEL | info | Minimum level for console (stdout) output |
ACP_LOG_FILE_LEVEL | debug | Minimum level for file output (global + session logs) |
ACP_LOG_AREAS | * | Comma-separated area filter. Use - prefix to exclude (e.g. -client,-sse) |
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.