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 logsverb. It maps to the handler incmd/sklp/logs.go, which callsGET /api/stacks/:id/logs. Flags listed below are the only flags accepted — anything else fails withflag 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--sinceto widen it. The default page size is 100, the server cap is 500.--sinceand--fromare 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:
- the token stays in the local config and never enters a prompt;
- the stack is derived from the project, so there is no extra id to know;
- time is relative (
--since 15m), not an absolute window to calculate; - output is one compact line per record, so a 500-log page does not saturate a context window.
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:
--stack ID— used verbatim, no derivation.- otherwise the ids from
--project, elseSKLP_PROJECT. - the first of those ids is looked up (
GET /api/projects/:id) to read itsstack_id. - 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
| Flag | Meaning |
|---|---|
--project ID[,ID...] | Comma-separated project ids. Falls back to SKLP_PROJECT. The first id also determines the stack. |
--stack ID | Stack id. Skips derivation entirely. |
--since D | Relative window (15m, 2h). Default 1h. Mutually exclusive with --from. |
--from T | Absolute RFC3339 start. |
--to T | Absolute RFC3339 end. |
--severity S | Server-side severity filter, e.g. ERROR. |
-q TEXT | Full-text search in message and attribute values. |
--source S | Origin filter: browser or server. |
--limit N | Page size. Default 100, server cap 500. |
--cursor C | Opaque cursor from a previous page's truncation notice. |
--json | Raw API payload instead of the compact lines. |
--no-color | Disable 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
--follow— no live tail. Polling introduces a long-lived process with its own lifecycle; deliberately out of v1.- Infrastructure logs (
/api/stacks/:id/infrastructure/logs) are a separate source and are not exposed here. - Auto-pagination beyond the server cap.
Exit codes and errors
- non-zero with
specify --project or --stack, or set SKLP_PROJECT— no target resolved. - non-zero with
--since and --from are mutually exclusive— pick one time form. - non-zero with
invalid --since (want e.g. 15m, 2h)— the duration did not parse. - non-zero with
project <id> belongs to no stack; pass --stack— the project is not attached to a stack. - non-zero with
token rejected (run sklp auth login)— the token is invalid or expired. - non-zero with
api <status>: <body>— the API rejected the request; body truncated to 200 characters.
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.