sklp logs — CLI reference

Debugging a deployed service used to mean opening the dashboard. `sklp logs` puts the same query in the terminal, with the credential kept in local config — so a coding agent can read production logs on its own.

For AI coding agents (Claude Code, Codex, Cursor)

This page is the source of truth for the sklp logs verb. It maps to the handler in cmd/sklp/logs.go, which calls GET /api/stacks/:id/logs. Flags listed below are the only flags accepted — anything else fails with flag provided but not defined.

Hard rules: you need a target (--project, SKLP_PROJECT, or --stack) and authentication (sklp auth login). The default window is the last hour — pass --since to widen it. The default page size is 100, the server cap is 500. --since and --from are mutually exclusive.

Read stderr. Truncation and empty-result notices go there, never to stdout. A page that hit the limit looks exactly like a complete one unless you read the notice.

Why this verb exists

The dashboard URL for a log query cannot be handed to an agent: it carries the session credential, it hardcodes a stack id the agent has no way to know, and its from is an absolute RFC3339 timestamp the agent must compute — a computation that fails silently, since an empty window is indistinguishable from "no errors".

sklp logs closes that gap:

Authentication

Run sklp auth login once. sklp logs talks to the Skalpai SaaS via the config's api block — deploy contexts cannot retarget it.

Usage

sklp logs [--project ID[,ID...]] [--stack ID]
          [--since 15m | --from RFC3339] [--to RFC3339]
          [--severity S] [-q TEXT] [--source browser|server]
          [--limit N] [--cursor C] [--json] [--no-color]

Choosing the target

There is no SKLP_STACK variable. SKLP_PROJECT stays the single variable to know, exactly as for sklp issue.

Resolution order:

  1. --stack ID — used verbatim, no derivation.
  2. otherwise the ids from --project, else SKLP_PROJECT.
  3. the first of those ids is looked up (GET /api/projects/:id) to read its stack_id.
  4. every id passed is forwarded as project_ids, narrowing the query inside that stack.

So --project selects both the stack (via the first id) and the subset of projects to show. Passing several ids from different stacks is not meaningful: ownership is authorised on the stack, and a foreign project id simply matches no row.

With no target and no --stack, the command fails with specify --project or --stack, or set SKLP_PROJECT.

Choosing the time range

--since is the primary form and takes a Go duration — 15m, 2h, 24h. It resolves locally to now - d.

--from / --to are the escape hatch for an absolute RFC3339 window. Passing both --since and --from is an error, not a silent precedence rule.

The default is --since 1h. An unbounded default would pull the whole retention window on the first call.

Output

One compact line per record, in the API's order:

2026-07-31T13:22:20Z ERROR  Synthiz/synthiz-core HTTP handler 5xx
2026-07-31T13:22:20Z ERROR  Synthiz/synthiz-core deepen failed

Columns: timestamp (UTC, RFC3339), severity, origin, message.

Origin is project/service — a project groups several services, and the service is usually the discriminant when triaging. The service is dropped when it is absent or identical to the project name, so single-service projects stay readable.

Colour

In a terminal, severity is colourised (red for ERROR/FATAL/CRITICAL, yellow for WARN, dim for DEBUG/TRACE), the timestamp is dimmed, and the origin is cyan. The message keeps the default foreground — it is the information.

Colour is suppressed automatically when stdout is not a terminal, so sklp logs | jq and any agent reading stdout get clean text. Override with --no-color, or the standard NO_COLOR environment variable.

Messages are collapsed to one line and capped at 120 characters; attributes, trace ids, and resource fields are omitted. Use --json when you need them.

Notices on stderr

stdout carries log lines only, so it stays pipeable into jq, grep, or awk. Two notices go to stderr:

# no logs in range
# 3 logs shown, more available — narrow --since or pass --cursor eyJ0cyI6…

The second one matters: without it, a truncated page is indistinguishable from a complete one.

--json

Emits the API payload unchanged — items, next_cursor, has_more — with every field the DTO carries.

sklp logs --since 6h --severity ERROR --json | jq '.items[].attributes'

Flags

FlagMeaning
--project ID[,ID...]Comma-separated project ids. Falls back to SKLP_PROJECT. The first id also determines the stack.
--stack IDStack id. Skips derivation entirely.
--since DRelative window (15m, 2h). Default 1h. Mutually exclusive with --from.
--from TAbsolute RFC3339 start.
--to TAbsolute RFC3339 end.
--severity SServer-side severity filter, e.g. ERROR.
-q TEXTFull-text search in message and attribute values.
--source SOrigin filter: browser or server.
--limit NPage size. Default 100, server cap 500.
--cursor COpaque cursor from a previous page's truncation notice.
--jsonRaw API payload instead of the compact lines.
--no-colorDisable ANSI colour. Also honours $NO_COLOR; colour is off automatically when stdout is piped.

Examples

# errors in the last hour, project from SKLP_PROJECT
sklp logs --severity ERROR

# a specific incident window, across two projects of the same stack
sklp logs --severity ERROR \
  --from 2026-07-31T08:00:00Z --to 2026-07-31T09:00:00Z \
  --project 5e64fe65-...,eb680ac3-...

# hunt a symptom across the whole stack
sklp logs -q "context deadline exceeded" --since 24h --limit 500

# browser-side errors only
sklp logs --source browser --severity ERROR --since 2h

# extract attributes for one trace
sklp logs --since 30m --json | jq '.items[] | select(.trace_id != "")'

Pagination

sklp logs fetches a single page. When the server reports more rows, the stderr notice carries the cursor for the next one:

sklp logs --severity ERROR --since 24h --limit 500
# 500 logs shown, more available — narrow --since or pass --cursor eyJ0cyI6…

sklp logs --severity ERROR --since 24h --limit 500 --cursor eyJ0cyI6…

Narrowing --since is usually better than walking pages.

Not supported

Exit codes and errors

See also

The design rationale (why the stack is derived rather than configured, what was deferred) lives in docs/sklp/design/logs.md in the repo — internal notes, not published.