Create, message, poll and stop Claude Code, Codex and Copilot sessions through the Cogpit HTTP API on localhost. Use when an agent needs to start a session in a project, send a follow-up to a running session, wait for a turn to finish, read what a session did, or list Cogpit projects and active sessions.
Installs into .claude/skills of the current project.
Are you the author of Cogpit Sessions?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/gentritbiba-cogpit-sessions)
---
name: cogpit-sessions
description: Create, message, poll and stop Claude Code, Codex and Copilot sessions through the Cogpit HTTP API on localhost. Use when an agent needs to start a session in a project, send a follow-up to a running session, wait for a turn to finish, read what a session did, or list Cogpit projects and active sessions.
---
# Cogpit sessions API
## Base URL and port
The packaged app binds an ephemeral port unless network access pins 19384. Resolve the port in this order:
```bash
PORT="${COGPIT_PORT:-$(cat ~/.cogpit/port 2>/dev/null || echo 19384)}"
BASE="http://localhost:$PORT"
```
`~/.cogpit/port` is written on server start and removed on exit. All endpoints accept and return JSON. Local requests (127.0.0.1/::1) bypass authentication.
## CRITICAL: permissions
The server defaults to permission mode `default`, which gates tool calls behind interactive approval. A headless caller has no one to click Approve, so the session stalls on its first gated tool call. **Always pass:**
```json
"permissions": { "mode": "bypassPermissions" }
```
Full shape:
```json
{
"mode": "bypassPermissions" | "default" | "plan" | "acceptEdits" | "dontAsk" | "auto",
"allowedTools": ["Bash", "Read", "Write"],
"disallowedTools": []
}
```
- `bypassPermissions` runs every tool without prompting (`claude --dangerously-skip-permissions`). Use this from agents.
- Any other mode gates tool calls. Only use when a human is watching the Cogpit UI.
- `allowedTools` / `disallowedTools` are CLI tool names, applied as allow/deny lists in the gated modes.
- The old `{ "allow": [...], "deny": [...] }` shape is **silently ignored**. Sending it leaves the session in `default` mode and it hangs. Do not use it.
## Response timing
| Endpoint | Response time | Notes |
|----------|--------------|-------|
| `create-and-send` | 5–15 s | waits for the JSONL file to appear on disk |
| `send-message` | instant OR full turn | instant when the session's SDK query is live (the normal case after `create-and-send`); waits for the whole turn only when it must resume a cold session |
| everything else | instant | |
Use `--max-time 30` for `create-and-send`. For `send-message` use `--max-time 600` and run it in the background, since the resume path can take minutes.
**A 200 from `send-message` does NOT mean the turn finished.** Poll `/api/session-status/:sessionId` to detect completion (next section).
## Detecting turn completion
```bash
curl -s "$BASE/api/session-status/$SESSION_ID"
# → { "sessionId": "...", "live": true, "running": false, "status": "completed", "pendingQueue": 0 }
```
- `running`: a turn is in flight right now. This is the primary completion signal for sessions Cogpit manages: it flips true as soon as the server accepts a message (before `send-message` even responds) and false exactly at the turn boundary, so it does not suffer the JSONL flush lag that `status` does. Poll until it is `false`.
- `status`: one of `idle` | `thinking` | `tool_use` | `processing` | `completed` | `compacting` | `deferred` | `awaiting_agents`, derived from the session JSONL tail. Terminal statuses: `completed`, `idle`, `deferred`, or any `terminalReason`. **Non-terminal:** `awaiting_agents` means the turn ended but background agents/workflows are still running; the session will resume by itself when they notify. For sessions the server does not manage (started in a terminal, or before a server restart), treat terminal statuses as the end of turn. It can briefly report the previous turn's `completed` right after a send, so prefer `running` when it is available.
- `live`: the server holds an open SDK query or process that can take follow-ups without a resume. Stays `true` between turns for SDK and legacy sessions; native Codex sessions only report `live` during a turn.
- `pendingQueue`: user messages queued but not yet processed. Wait for it to hit 0 as well if you sent several messages back to back.
- `terminalReason`: set when the session ended abnormally.
- `pendingAgents`: (only when `status === "awaiting_agents"`) number of background agents/workflows still running.
- `pendingAgentDescriptions`: (only when `status === "awaiting_agents"`) short descriptions of pending agents, oldest first.
Poll loop:
```bash
sleep 2 # let the server accept the message you just sent
while [ "$(curl -s "$BASE/api/session-status/$SESSION_ID" | jq -r .running)" = "true" ]; do
sleep 5
done
```
## Create once, retry safely
Use one `requestId` per intended new session. Save the full JSON request before sending it; all retries must reuse the same file and ID. A parse failure or timeout does not prove the agent failed to start.
```bash
# Choose a task-specific directory and retain it until the result is confirmed.
REQUEST_DIR=$(mktemp -d)
jq -n --arg id "$(uuidgen)" \
--arg dir "-Users-gentritbiba-my-project" \
--arg message "What files are in this project?" \
'{requestId:$id, dirName:$dir, message:$message, permissions:{mode:"bypassPermissions"}}' \
> "$REQUEST_DIR/request.json"
curl -sS --max-time 30 -X POST "$BASE/api/create-and-send" \
-H 'Content-Type: application/json' \
--data-binary @"$REQUEST_DIR/request.json" \
-o "$REQUEST_DIR/response.json" -w '%{http_code}\n'
jq '{success, sessionId, error}' "$REQUEST_DIR/response.json"
```
On timeout, invalid response JSON, or a lost connection, repeat only the `curl` and `jq` commands with the saved request. Do not generate another ID or recreate the payload. Keep stderr separate from JSON. Do not pipe `echo "$RESULT"` into a JSON parser; some shells interpret the response's backslash escapes. Use the response file, or `printf '%s\n' "$RESULT"`.
- Success echoes `requestId` in the JSON response and returns the original session for the same ID and payload, including across server restarts. Same-process concurrent retries await the same creation.
- `409 CONFLICT` with a different-payload message means the ID was reused for different work. Restore the original payload to retrieve its result.
- `409 CONFLICT` with a pending/unknown-outcome message means another server is creating it, or the server stopped before recording the outcome. Retry the same ID later and inspect `/api/active-sessions`; do not launch another copy blindly. Stop retrying and report uncertainty if the result cannot be determined.
- IDs are scoped to the authenticated user, contain 8–128 letters, digits, hyphens or underscores, and have no automatic expiry. A fresh ID means an intentional new session.
- Older servers may ignore `requestId` and will not echo it in the response. On those, inspect active sessions after a failed response before any retry. Upgrade the server to get retry protection.
After confirmation, record the session ID and remove the temporary request directory. Request and response files may contain private prompt/transcript data.
The shorter examples below omit request persistence for readability. Use this pattern for every actual `create-and-send` call.
## Finding the dirName
The `dirName` is the project's absolute path with every non-alphanumeric character replaced by `-`:
```bash
# /Users/x/my.app → -Users-x-my-app
DIR_NAME=$(echo "/Users/x/my.app" | sed 's|[^a-zA-Z0-9]|-|g')
```
Discover existing projects instead of guessing:
```bash
curl -s "$BASE/api/projects"
# → [{ dirName, path, shortName, sessionCount, lastModified }]
```
Codex projects appear with `codex__<base64url-of-cwd>` dirNames and a `(Codex)` suffix on `shortName`. The same endpoints drive Codex sessions.
## API reference
### POST /api/create-and-send
Create a session and send the first message. Spawns a persistent SDK session that stays alive for follow-ups.
Body:
```json
{
"dirName": "string (required)",
"message": "string (required unless images provided)",
"images": [{ "data": "base64", "mediaType": "image/png" }],
"permissions": { "mode": "bypassPermissions" },
"model": "string (e.g. 'sonnet', '' for provider default, or a full model id; GET /api/models lists options)",
"effort": "'low' | 'medium' | 'high' | 'xhigh' | 'max'",
"fastMode": "boolean (fast/priority service tier)",
"ultracode": "boolean (Claude ultracode; needs xhigh-capable model)",
"requestId": "string (reuse with the same payload for safe retries)",
"worktreeName": "string (runs the session in a git worktree with this name)",
"mcpConfig": "string (JSON-encoded mcpServers config, passed to the SDK)",
"name": "string (session name)",
"cwd": "string (optional absolute path; must encode to dirName)"
}
```
Response 200: `{ success, dirName, fileName, sessionId, initialContent }`. Error 400/500: `{ error }`.
### POST /api/send-message
Send a follow-up. Enqueues on the live SDK query if the session is still running, otherwise resumes it.
Body: `{ "sessionId": "...", "message": "...", "images": [...], "cwd": "...", "permissions": {...}, "model": "...", "effort": "...", "fastMode": ..., "ultracode": ..., "mcpConfig": "..." }`
`sessionId` plus `message` or `images` are required. `permissions` and `cwd` only apply on the resume path. Response: `{ success: true }`. See the timing section: poll `session-status` for completion.
### POST /api/interrupt-session
`{ "sessionId": "..." }`. Interrupts the current turn but keeps the session alive for the next message. Response: `{ success: boolean }`.
### POST /api/stop-session
`{ "sessionId": "..." }`. Kills the session's process/query. Response: `{ success: true }`, or `{ success: false, error }` when nothing was running.
### POST /api/kill-all
Kill every agent process Cogpit manages. Response: `{ success, killed }`.
### POST /api/delete-session
`{ "dirName": "...", "fileName": "..." }`. Kills the session and permanently deletes its JSONL file.
### POST /api/archive-sessions
Archive or restore sessions on the server. Archiving only hides a session from the sidebar; the transcript is untouched.
Body: `{ "sessionIds": ["...", "..."], "archived": true }`
- `sessionIds` — array of 1–500 session IDs to archive or restore (duplicates are silently deduplicated)
- `archived` — true to archive, false to restore and mark the session kept (exempt from auto-archive)
Response: `{ sessionIds, archived, changed }` where `changed` is the list of session IDs whose archive state actually changed (empty array if all were already in the target state).
#### Archive behavior
- **Manual archive**: user archives a session via the sidebar. The server records `archived: true` with the archive timestamp.
- **Auto-archive**: after 14 days of transcript inactivity, the session is archived unless the user restored it (it is in `kept`).
- **Auto-unarchive**: when a transcript is written after archiving (e.g., resuming a session or sending a message from Cogpit), the session comes back on its own if the write is well after the archive action (more than 2 minutes).
- **Restore**: user unarchives a session. It is added to `kept`, which prevents the auto-archive rule from archiving it again until the user manually archives it.
### GET /api/projects
All projects with sessions: `[{ dirName, path, shortName, sessionCount, lastModified }]`.
### GET /api/sessions/:dirName?page=1&limit=20
Paginated session list for a project, newest first: `{ sessions, total, page, pageSize }`. Each session shares the shape of `/api/active-sessions` (see below): `sessionId`, `fileName`, `size`, `lastModified`, `lastActivityAt`, `model`, `gitBranch`, `turnCount`, `agentStatus`, `agentToolName`, `agentTerminalReason`, `agentPendingAgents`, and optional `pullRequests` (when the index has scanned the transcript). Plus session metadata (`cwd`, `firstUserMessage`, `lastUserMessage`, `timestamp`, `aiTitle`, `customTitle`, `version`, ...).
### GET /api/sessions/:dirName/:fileName
Raw session JSONL as `text/plain`. For large sessions page it:
- `?tail=N` returns the last N turns' worth of lines as JSON: `{ headerLines, tailLines, byteOffset, totalSize, hasMore }`
- `?before=<byteOffset>&count=N` pages backward from a previous response's `byteOffset`: `{ headerLines, lines, byteOffset, hasMore }`
### GET /api/session-context/:sessionId
Parsed session overview: turn summaries, tool call counts, token totals. Much easier to consume than raw JSONL. Drill down with `/turn/:turnIndex` (full turn detail) and `/agent/:agentId` (subagent transcripts, plus `/agent/:agentId/turn/:i`). Prefer this for reading what a session did.
### GET /api/session-status/:sessionId
`{ sessionId, live, running, status, toolName?, pendingQueue?, terminalReason?, pendingAgents?, pendingAgentDescriptions? }`. See "Detecting turn completion". 404 if the session doesn't exist. `pendingAgents` and `pendingAgentDescriptions` are only present when `status === "awaiting_agents"`.
### GET /api/find-session/:sessionId
Resolve a bare sessionId to `{ dirName, fileName }`.
### GET /api/active-sessions
Recent sessions across all projects, newest first. `?search=<q>` filters by title/message/branch/cwd content. It also accepts exact pull request searches such as `#157`, `honest-cms #157`, `honest-cms#157`, `PR 157`, and a pasted GitHub pull request URL. PR searches cover sessions that created the pull request or used an explicit `gh pr` action for it. The first search may build the durable transcript index in the background. Poll the same request until `X-Cogpit-PR-Index-Pending` is `0`; `X-Cogpit-PR-Index-Total` reports the number of candidate transcripts. A matching row includes `matchedPullRequestNumber`.
Query parameters:
- `?project=<dirName>` — Only that project's sessions, up to `?limit=` (default 50, max 200) instead of the per-project cap
- `?archived=include` — Include archived sessions in the results (dimmed; auto-filtered out by default)
Response headers:
- `X-Cogpit-Archived-Count` — Number of archived sessions that matched the query
Fields per session: `dirName`, `projectShortName`, `fileName`, `sessionId`, `cwd`, `gitBranch`, `model`, `turnCount`, `lastActivityAt`, `agentStatus` (same values as session-status), `agentToolName`, `agentPendingAgents` (present when status is awaiting_agents), `pullRequests`, `archived` (boolean, present when true), `archivedReason` (string: `"manual"` when user archived it, `"inactive"` when auto-archived), and for team members `teamName`, `agentName`, `teamLeadSessionId`.
### GET /api/running-processes
System-wide agent processes with PID, memory, CPU, and sessionId.
### GET /api/agent-executable/:kind
Cogpit's choice of which Claude Code binary to spawn, and all available options. Only Claude has a choice (the Agent SDK vendors a copy); Codex and Copilot run whatever `PATH` offers, so this endpoint returns 404 for them.
Response 200: `{ choice, candidates, active }` where:
- `choice` — the user's stored preference: `{ source: "auto" | "path" | "npm" | "bundled" | "custom", path?: string }`
- `candidates` — each detected binary: `[{ source, path, version }]`
- `active` — the one Cogpit will spawn: `{ source, path, version }` or `null` if nothing is launchable given the choice
Response 404: agent has no choice to make (e.g., `GET /api/agent-executable/codex`).
## Typical agent workflow
```bash
PORT="${COGPIT_PORT:-$(cat ~/.cogpit/port 2>/dev/null || echo 19384)}"
BASE="http://localhost:$PORT"
# 1. Discover the project
DIR_NAME=$(curl -s "$BASE/api/projects" | jq -r '.[0].dirName')
# 2. Start a session (--max-time 30 required; response takes 5-15s)
RESULT=$(curl -s --max-time 30 -X POST "$BASE/api/create-and-send" \
-H "Content-Type: application/json" \
-d "{\"dirName\": \"$DIR_NAME\", \"message\": \"List the main source files\", \"permissions\": {\"mode\": \"bypassPermissions\"}}")
SESSION_ID=$(printf '%s\n' "$RESULT" | jq -r '.sessionId')
# 3. Send a follow-up in the background
curl -s --max-time 600 -X POST "$BASE/api/send-message" \
-H "Content-Type: application/json" \
-d "{\"sessionId\": \"$SESSION_ID\", \"message\": \"Now fix the failing tests\"}" \
> /tmp/send-result.txt 2>&1 &
# 4. Poll until the turn completes
sleep 2
while [ "$(curl -s "$BASE/api/session-status/$SESSION_ID" | jq -r .running)" = "true" ]; do
sleep 5
done
# 5. Read what happened (parsed, no JSONL wrangling)
curl -s "$BASE/api/session-context/$SESSION_ID"
# 6. Stop when done
curl -s -X POST "$BASE/api/stop-session" \
-H "Content-Type: application/json" \
-d "{\"sessionId\": \"$SESSION_ID\"}"
```
## Fire-and-forget
Start a task and check on it later. The server-side process persists; no connection needs to stay open.
```bash
RESULT=$(curl -s --max-time 30 -X POST "$BASE/api/create-and-send" \
-H "Content-Type: application/json" \
-d '{"dirName":"...","message":"Do the task","permissions":{"mode":"bypassPermissions"}}')
SESSION_ID=$(printf '%s\n' "$RESULT" | jq -r '.sessionId')
# Later:
curl -s "$BASE/api/session-status/$SESSION_ID"
curl -s "$BASE/api/session-context/$SESSION_ID"
```
## Notes
- The server must be running (Cogpit app, or `bun run dev` in the agent-window project).
- Claude sessions persist as JSONL in `~/.claude/projects/<dirName>/`; Codex rollouts live in Codex's own sessions tree but are served through the same endpoints.
- The SDK session stays alive between messages, so follow-ups have no cold start and skip permission re-negotiation.
- Always pass `{"mode": "bypassPermissions"}`; the legacy `{allow, deny}` permissions shape is ignored and leaves the session hanging in `default` mode.