Skip to content

How to Store Claude Code or Codex Traces in Local DuckLake

Run the duckdb-otlp server image locally and point Claude Code or Codex at it with OTLP/HTTP. The container listens on localhost:4318 and writes traces into a DuckLake catalog stored in a Docker volume.

The examples follow the Claude Code monitoring docs and the Codex observability and configuration docs.

Create a Docker volume for DuckLake metadata and Parquet data:

Terminal window
docker volume create duckdb-otlp-ducklake

Create .env:

DUCKDB_MODE=local-ducklake
DUCKLAKE_NAME=lake
DUCKDB_OTLP_TOKEN=dev-otlp-token-123456
DUCKDB_QUACK_ENABLED=1
DUCKDB_QUACK_ADDR=0.0.0.0:9494
DUCKDB_QUACK_TOKEN=dev-quack-token-123456

Start the published server image:

Terminal window
docker run --rm --name duckdb-otlp \
--env-file .env \
-p 4318:4318 \
-p 9494:9494 \
-v duckdb-otlp-ducklake:/data \
ghcr.io/smithclay/duckdb-otlp:latest

This starts duckdb-otlp at http://localhost:4318. Send OTLP/HTTP traces to:

http://localhost:4318/v1/traces

The token in .env is:

dev-otlp-token-123456

To use a different local token, change DUCKDB_OTLP_TOKEN and use the same value in the agent exporter headers.

Run Claude Code with tracing enabled and route spans to the local writer:

Terminal window
# To make this apply to every Claude Code session, add these to ~/.claude/settings.json
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1
export OTEL_TRACES_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
export OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer dev-otlp-token-123456'
claude -p "write one sentence about local trace storage"

To store Claude Code events in DuckLake, add the logs exporter before starting Claude Code:

Terminal window
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://localhost:4318/v1/logs

Leave OTEL_LOG_USER_PROMPTS, OTEL_LOG_TOOL_DETAILS, OTEL_LOG_TOOL_CONTENT, and OTEL_LOG_RAW_API_BODIES unset unless you intend to store prompt text, tool details, tool content, or raw API bodies.

Put telemetry routing in your user-level Codex config. Codex ignores otel in project-local .codex/config.toml.

Edit ~/.codex/config.toml:

[otel]
environment = "local"
log_user_prompt = false
metrics_exporter = "none"
exporter = { otlp-http = {
endpoint = "http://localhost:4318/v1/logs",
protocol = "binary",
headers = { "Authorization" = "Bearer dev-otlp-token-123456" }
}}
trace_exporter = { otlp-http = {
endpoint = "http://localhost:4318/v1/traces",
protocol = "binary",
headers = { "Authorization" = "Bearer dev-otlp-token-123456" }
}}

Start a new Codex process after editing the config:

Terminal window
codex exec "write one sentence about local trace storage"

To collect spans without Codex event logs, set exporter = "none" and keep the trace_exporter block.

duckdb-otlp buffers accepted rows. Flush before you query a short local run:

Terminal window
duckdb <<'SQL'
INSTALL quack;
LOAD quack;
FROM quack_query(
'quack:localhost:9494',
'SELECT * FROM otlp_flush(''otlp:0.0.0.0:4318'')',
token = 'dev-quack-token-123456'
);
SQL

Query through the running DuckDB process with Quack. The server process owns the DuckLake catalog lock while it runs, and the distroless image has no shell or bundled DuckDB CLI, so do not use docker exec ... sh -c for inspection SQL.

Terminal window
duckdb <<'SQL'
INSTALL quack;
LOAD quack;
FROM quack_query(
'quack:localhost:9494',
$$
SELECT trace_id, name, service_name, duration_time_unix_nano
FROM lake.main.otlp_traces
ORDER BY start_time_unix_nano DESC
LIMIT 20
$$,
token = 'dev-quack-token-123456'
);
SQL

If you enabled event logs, inspect recent log rows:

Terminal window
duckdb <<'SQL'
INSTALL quack;
LOAD quack;
FROM quack_query(
'quack:localhost:9494',
$$
SELECT time_unix_nano, service_name, severity_text, body
FROM lake.main.otlp_logs
ORDER BY time_unix_nano DESC
LIMIT 20
$$,
token = 'dev-quack-token-123456'
);
SQL

Stop the container with Ctrl-C if your terminal is attached, or run:

Terminal window
docker stop duckdb-otlp

During shutdown, the image sends otlp_stop('otlp:0.0.0.0:4318') to DuckDB so the process commits remaining buffered rows before it exits.

  • Start the agent process after you set the telemetry settings.
  • Confirm the trace endpoint is http://localhost:4318/v1/traces.
  • Confirm the Authorization header uses the same token as DUCKDB_OTLP_TOKEN.
  • Flush the writer before querying short sessions.
  • Keep the query window recent enough to include the agent run.