Import selected Codex, Claude Code, OpenClaw, and Hermes sessions into a dedicated, redacted, searchable archive that stays separate from your curated vault, manually or through opt-in automation (a Claude Code SessionEnd hook or a daemon schedule).
GNO can make selected local agent conversations searchable while keeping who said what, when, and where. Supported harnesses are Codex, Claude Code, OpenClaw, and Hermes. Each import writes a sanitized archive you own, then indexes it through the ordinary JSONL record path, so search, query, ask, get, and Context Capsules work on it without a separate ranking engine.
Imported dialogue is source evidence, not truth. An assistant suggestion is labelled as assistant output and never shown as a user decision. Nothing is promoted to memory: turning something from a session into a standing fact stays an explicit gno remember call. See Memory.
gno_sessions_import call, REST request, or SDK call, until you explicitly enable automation for a profile.Your curated vault holds notes you chose to keep. Session logs are high-volume, repetitive, and full of assistant guesses. The recommended setup therefore keeps them in a dedicated archive outside the vault, with its own config file and its own named index. Plain gno update, gno search, and your curated MCP or Web UI server never read the archive unless you opt in.
~/gno-sessions/archive.yml) bound to one named index (for example sessions). Every archive command passes both: gno --config ~/gno-sessions/archive.yml --index sessions <command>.SESSIONS_BINDING_MISMATCH.sessions init refuses the default config file and the default index. The archive root must sit outside GNO's config, data, and cache directories, so gno reset, index cleanup, and uninstall cannot delete it.sessions init also refuses an archive root inside a folder your default (curated) config already indexes. A vault folder no curated collection covers is fine; to search sessions from the curated index, opt in with mixed retrieval after init.Preview what exists on this machine first. Discovery reports session roots, unit counts, sizes, and sampled format versions. It never imports and needs no archive.
gno sessions discoverDiscovery looks in $CODEX_HOME/sessions, else ~/.codex/sessions; ~/.claude/projects and $CLAUDE_CONFIG_DIR/projects; $OPENCLAW_STATE_DIR or ~/.openclaw; and $HERMES_HOME or ~/.hermes. Then create the archive and register the sources you approve:
gno --config ~/gno-sessions/archive.yml --index sessions sessions init --archive ~/gno-sessions/archive --collection sessions-work
gno --config ~/gno-sessions/archive.yml --index sessions sessions source add codex --harness codex --path ~/.codex/sessions --collection sessions-work
gno --config ~/gno-sessions/archive.yml --index sessions sessions source add claude --harness claude-code --path ~/.claude/projects --collection sessions-work--archive must be an absolute directory (the shell expands ~). Init is idempotent, records the archive binding in the named index immediately, refuses an index that already holds other collections, and never retargets a config already bound to another index or root. Source IDs and collection names are lowercase letters, digits, hyphens, and underscores. Registering a source never imports.
Route projects to their own collections with --project <prefix>=<collection>, repeatable. Threads recorded under that working-directory prefix land in the mapped collection:
gno --config ~/gno-sessions/archive.yml --index sessions sessions source add codex-client --harness codex --path ~/.codex/sessions --collection sessions-work --project ~/work/client-a=sessions-client-aA thread whose working directories map to different collections is quarantined (skipped_policy, reason mixed_domain) instead of being written to the less restricted one. Add a --project mapping that resolves all its directories to one collection and the next import archives it. Changing a source's collection or mappings re-imports the affected threads on the next run. Removing a source with sessions source remove <id> keeps its archive.
gno --config ~/gno-sessions/archive.yml --index sessions sessions import --source codex --dry-run
gno --config ~/gno-sessions/archive.yml --index sessions sessions import --source codex
gno --config ~/gno-sessions/archive.yml --index sessions embed--dry-run parses and reports, including redaction counts and destination collections, without writing archive files, checkpoint state, or index rows.--limit <n> bounds how many changed units one run processes; unchanged units do not count and the rest are reported as deferred.sessions import <paths...> --collection <name> [--format codex|claude-code|openclaw|hermes]. Paths must be absolute; --collection is rejected together with --source.Reruns are idempotent. A unit is skipped when its fingerprint and the parser, redaction, and archive-format versions are unchanged; a changed unit is re-rendered and compared byte for byte. Turn IDs stay stable across appends, so a growing session updates its archive file and the index reconciles its records. Imports on one archive serialize; a second concurrent import fails as busy.
Session search is ordinary search, query, ask, get, and Context Capsules against the archive pair. Broad search in your curated index does not see archives.
gno --config ~/gno-sessions/archive.yml --index sessions query "why did we pick sqlite" --category harness/codex --author human
gno --config ~/gno-sessions/archive.yml --index sessions search "migration plan" --tags-all project/api,role/humanHarness and project identity are tags, so one project stays searchable across agents:
harness/codex, harness/claude-code, harness/openclaw, harness/hermesproject/<basename> and project-id/<hash>, which tells apart projects that share a folder namerole/human, role/assistant; the author field is human or assistantsession-kind/main, subagent, fork, or continuation, plus session on every recordNothing federates automatically, and there is no cross-index server context. To search sessions from your curated index, register an archive collection folder in the curated config yourself:
gno collection add ~/gno-sessions/archive/sessions-work --name sessions-work --pattern '**/*.jsonl'Then copy that collection's recordAdapters.jsonl.fieldMapping block from the archive config into the new collection entry in the curated config, so author, tags, session, and date metadata keep working:
collections:
- name: sessions-work
path: /absolute/path/to/gno-sessions/archive/sessions-work
pattern: "**/*.jsonl"
recordAdapters:
jsonl:
fieldMapping:
id: /id
title: /title
body: /body
author: /author
categories: /categories
sessionId: /sessionId
threadId: /threadId
dateFields:
recorded: /recordedAtFrom then on, session records take part in that index's ordinary retrieval, graph, listing, and tag behaviour, and any agent connected to the curated index can read them. Only do this for collections whose content you are comfortable exposing there.
Human speech is kept only where the harness structurally marks it as human; assistant final text is kept. Excluded by default:
codex exec, Claude Code SDK and claude -p sessions), because a program or another agent may have written them; their assistant replies are archivedSlash commands a person typed in Claude Code are archived as /name args. Forks and subagents stay distinct threads linked to their parent. Text that imitates another speaker inside a message stays quoted content. Unknown record kinds produce bounded, content-free drift diagnostics. The archive is a record of dialogue, not a complete execution audit.
Each archived turn is one record. Its body starts with Human: or Assistant: and ends with one Provenance: line whose fields are separated by · :
recorded unknown when the source has no timestamp; a known time is the record dateTitles read like Human · Codex · api. Thread and session IDs, harness, project and role travel as record fields and tags. Cite session evidence with the record's gno:// URI; URIs from the archive index carry ?index=sessions.
Every import returns the same receipt on the CLI, MCP, REST, and SDK. Receipts, status, and diagnostics carry no session content and no host paths.
complete, partial, failed, or nothing_to_do. Any incomplete, failed, unsupported, or deferred unit, or a failed lexical sync, makes the run partial; a run in which nothing imported and nothing is incomplete is failed. A partial import is never reported as complete. A rerun over unchanged sources counts their archived threads as unchanged.truncated_tail, format_drift, snapshot_read_failed, permission_denied, source_missing, and format_not_recognised.A file still being written (its final line is cut) or one showing format drift is incomplete: readable threads are archived, the checkpoint does not advance, and the next run retries it. Archive writes are atomic, so an interrupted import heals on rerun without duplicates. Units count as complete only after the index sync succeeds; a failed or interrupted sync is retried by the next import. Units with malformed records stay incomplete. A thread withheld by policy (quarantined, or at a limit, which is skipped whole rather than archived truncated) has any earlier archived copy removed from search. When a registered source root is missing or cannot be read, import still rescans its retained archive for new redaction rules, then fails with SESSIONS_SOURCE_UNAVAILABLE (exit 2) instead of reporting the source as up to date. A directory or session file inside a source that cannot be read is a failed unit (a directory has the locator ".", since its name can be a host path), so the run is partial, never nothing_to_do, and those units keep their checkpoints until they are read.
Error codes and exit statuses are listed under Error codes.
Parsers are verified against the store layouts below. Where no live store was available, the table says so.
| Harness | Store | Verified against |
|---|---|---|
| Codex | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl | Local store structure, CLI 0.58 through 0.156.1 (September 2026). Covers the legacy user/agent message events (0.150 and earlier) and completed-item speech events (0.151 and later). |
| Claude Code | ~/.claude/projects/<dir>/<session>.jsonl, subagent files under <session>/subagents/, and $CLAUDE_CONFIG_DIR/projects | Local store structure, CLI 2.1.2xx (September 2026). Files without the origin field use a conservative fallback that could not be verified against a real file. |
| OpenClaw | ~/.openclaw/agents/<agent>/agent/openclaw-agent.sqlite (schema 23) and legacy agents/<agent>/sessions/*.jsonl | Upstream v2026.9.6 source definitions, tested with synthetic fixtures; not verified against a live installation. zstd-compressed events are supported. |
| Hermes | ~/.hermes/state.db and profiles/<name>/state.db | Hermes v0.19.0 schema (version 22) from the installed source, tested with synthetic fixtures; no populated store was available. |
Consumer chat exports keep using the generic JSONL and transcript export adapters. Account connectors are out of scope.
Limits: a source unit up to 512 MiB; a JSONL line up to 32 MiB (larger lines are skipped and counted); a turn up to 256 Ki characters (skipped and counted); up to 20,000 turns and 64 MiB of archive per thread (larger threads are skipped as over_limit); up to 100,000 units per source per run.
.gno-sessions/state.json checkpoint) and the archive config. Back them up like any folder.gno --config ~/gno-sessions/archive.yml --index sessions update and embed again. A lost checkpoint only costs a full re-render on the next import; the archive files are the source of truth.sessions prune --source <id> previews archived units whose source is gone; --apply deletes exactly those archive files and syncs the index. An archive file that a still-present unit references is never pruned, so a session file the harness moved keeps its archive. Prune needs a complete listing: when part of the source cannot be read, it fails with SESSIONS_SOURCE_UNAVAILABLE rather than treating unread units as deleted. If the index sync fails, prune records nothing and the next prune retries.gno --config ~/gno-sessions/archive.yml --index sessions sessions status
gno --config ~/gno-sessions/archive.yml --index sessions sessions prune --source codex
gno --config ~/gno-sessions/archive.yml --index sessions sessions prune --source codex --applyAutomation imports new session turns without you running sessions import each time. It is off until you switch it on, and it only ever calls the same importer as a manual import: the same sources, destinations, redaction, checkpoints, and receipts. Installing, upgrading, repairing, or restarting GNO never enables it, and a paused trigger stays paused.
Two triggers exist, each switched on separately for one profile:
| Trigger | What fires it | What it does in the foreground |
|---|---|---|
| Claude Code SessionEnd hook | Claude Code ending a session | Marks the profile pending on disk, then returns |
| Daemon schedule | gno daemon on this archive, every cadence | Marks the profile pending when due |
Neither trigger imports anything by itself. The import runs later, in one of two places:
gno daemon on the archive's config and index drains pending profiles at startup and every 30 seconds.gno sessions automation run <profile> runs a profile now, in the foreground.Claude Code SessionEnd ──► gno sessions hook ──┐
├─► pending marker (per profile,
daemon schedule tick (due) ────────────────────┘ durable, coalesced)
│
gno daemon tick / automation run ▼
manual importer (sessions import)With no daemon running, hook events stay pending (reported as pending, not yet archived) until a daemon starts or you run the profile. gno serve never drains pending work or runs schedules.
| Trigger | Status |
|---|---|
| Claude Code SessionEnd hook | Supported. Verified with Claude Code 2.1.280: SessionEnd fires for non-interactive claude -p runs. |
Claude Code interactive /exit | Unverified: whether SessionEnd fires when an interactive session ends with /exit was not verified. The daemon schedule catches up either way. |
| Codex, OpenClaw, and Hermes hooks | Not supported. enable --hook codex fails with SESSIONS_UNSUPPORTED_INTEGRATION; import manually or on a daemon schedule. |
| Pre-compaction triggers | Not supported in any harness. Manual or daemon schedule only. |
| Daemon schedule | Supported for every registered source; ticks only inside gno daemon on the archive's config and index. |
A="gno --config ~/gno-sessions/archive.yml --index sessions"
# 1. A profile names registered sources. It enables nothing.
$A sessions automation set claude --source claude-code
# 2. Review what would run: sources, destinations, the exact hook command,
# the settings file it edits, and the daemon prerequisite.
$A sessions automation preview claude
# 3. Switch on the Claude Code SessionEnd hook, the schedule, or both.
$A sessions automation enable claude --hook claude-code
$A sessions automation enable claude --schedule --cadence 30m
# 4. Start the daemon that performs the imports (GNO never starts it for you).
$A daemon --detach
# 5. Watch it work.
$A sessions statusA profile lives in the archive config under sessions.automation:
sessions:
index: sessions
archiveRoot: /absolute/path/to/gno-sessions/archive
sources:
- id: claude-code
harness: claude-code
path: /absolute/path/to/.claude/projects
collection: sessions-work
automation:
- id: claude
sources: [claude-code]
hook:
harness: claude-code
enabled: true
settings: /absolute/path/to/.claude/settings.json
schedule:
enabled: true
cadence: 30m
limit: 200 # optional: changed units per source per run
retries: 3 # optional: automatic retries after a failed runsources must be registered sources (1 to 64). Destinations, project mappings, and privacy boundaries stay on those sources; a profile cannot add a source or change where turns land. Reconfigure with sessions automation set.hook and schedule appear only after enable, and enabled changes only through enable and disable (or your own edit of the file).limit (default 200) bounds each run. When more changed units wait, the rest stay pending and the next daemon tick continues. retries is 0 to 10 (default 3).Run state (pending markers, last run, next due time, daemon heartbeat) is machine-written to <archiveRoot>/.gno-sessions/automation.json with mode 0600. It is not configuration; deleting it only forgets pending work and history.
enable --hook claude-code adds one entry to a Claude Code settings file, by default $CLAUDE_CONFIG_DIR/settings.json, else ~/.claude/settings.json, which is created when missing. Another file can be chosen only with the CLI (--settings <absolute path>), and it must already exist; the Web UI and REST always use the recorded or default file:
{
"hooks": {
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "GNO_DATA_DIR='…' GNO_CACHE_DIR='…' '/path/to/bun' run '/path/to/gno/src/index.ts' --config '/absolute/path/to/gno-sessions/archive.yml' --index 'sessions' sessions hook claude-code --profile 'claude'",
"timeout": 5
}
]
}
]
}
}settings.json.bak, and a settings file that is not valid JSON is left untouched with an error.enable after moving GNO or changing GNO_DATA_DIR; it repairs the entry in place. Enabling with a different --settings file first removes the entry from the previous file.accepted (profile claude pending, not yet archived; …), skipped (…), or not accepted (…); nothing was archived with a non-zero exit. It never reports archival. An unknown profile is reported as skipped (… unknown_profile), a profile whose hook is off as skipped (… hook_disabled).GNO_SESSIONS_HOOKS=off (or 0) in Claude Code's environment makes every installed hook return immediately.Verified against Claude Code 2.1.280: SessionEnd fires for non-interactive claude -p runs with the fields session_id, transcript_path, cwd, hook_event_name, reason, and prompt_id, and the hook finishes inside Claude Code's default 1.5-second SessionEnd budget. Whether SessionEnd fires when an interactive session ends with /exit was not verified; the schedule catches up either way.
gno daemon running on the archive's config and index. Enabling a schedule does not install or start a service; start the daemon yourself (for example daemon --detach, or under your own login item or service manager).--cadence uses the daemon cadence grammar <n>s|m|h|d, from 1m to 30d. It is elapsed time, not a wall-clock time of day, so daylight-saving changes do not move it. There is no cron syntax.enable, like the daemon findings pass.sessions status shows them in your local timezone and names it.Only one resident process (gno serve or gno daemon) may use a GNO data directory. If your curated gno serve or gno daemon already runs, the archive daemon cannot start on the same data directory (Resident runtime already active). Give the archive its own data directory and use it for every archive command, including enable:
export GNO_DATA_DIR=~/gno-sessions/data
gno --config ~/gno-sessions/archive.yml --index sessions update # rebuilds the disposable index there
gno --config ~/gno-sessions/archive.yml --index sessions sessions automation enable claude --hook claude-code
gno --config ~/gno-sessions/archive.yml --index sessions daemon --detachgno sessions automation run <profile> runs one profile through the importer immediately and records the outcome in automation status, whether or not any trigger is enabled. An explicit run resets any backoff and retry budget. It returns not_started (busy) only while another automation run of the same profile is in progress; a manual import holding the archive shows as a failed run with reason busy, retried with backoff.
gno sessions status (and every status surface) reports, per profile:
| Field | Meaning |
|---|---|
state | off, idle, pending, running, retrying, partial, or failed |
hook | Harness, enabled, and whether the owned entry is present in its settings file |
schedule | Enabled, cadence, and nextDueAt (null unless a daemon is running) |
pending | Since when, and which triggers admitted it |
running | A live run and when it started |
lastRun | Triggers, times, outcome, reason code, thread and unit counts |
lastSuccessAt | Last run that completed fully (complete or up_to_date) |
retryAt | When a failed run is retried |
recovery | The next action to take, when one is needed |
A schedule whose cadence was hand-edited to an invalid value (for example banana, or 5s below the minimum) is reported with enabled: false, a warning, and a recovery action (in status and in preview), and never runs until you fix it with sessions automation set … --cadence.
The daemon writes its heartbeat every 30 seconds on its own timer, so it stays running during its initial sync and during long imports. The daemon block reports running only with a fresh heartbeat from a live daemon on this archive, stale for a live process that stopped ticking, and not_running otherwise; then the schedule shows not running: no daemon instead of a due time.
A profile in failed state keeps its admitted work in the JSON pending field, but the text status and the Web UI show the failure and its recovery action instead of a pending line, because that work only runs after you fix the cause.
A run's outcome is complete, up_to_date (a verified no-op: every source was already current), partial (incomplete or deferred units, or a failed index sync; the next run retries them), or failed. Missing or unreadable sources, stale heartbeats, and failed runs never appear as a successful completion. Status, logs, and receipts carry counts, IDs, and reason codes only: no session content and no host paths.
| Reason code | Cause | What happens |
|---|---|---|
busy | Another import or index writer holds the archive or index (including a locked SQLite index) | A running daemon retries it with backoff (1m, 2m, 4m, … up to 30m), retries times; without a daemon, run it again yourself. automation run exits 4 (SESSIONS_BUSY). |
runtime_error | Filesystem or index failure | Retried with backoff |
interrupted | The process running it died | Pending work kept; the next daemon tick reruns it |
source_revoked | A profile source is no longer registered | No automatic retry; fix the profile or re-register the source |
source_unavailable | A source or the archive directory is missing or unreadable | No automatic retry; fix it, then automation run |
invalid_configuration | Collections, binding, or source settings no longer match | No automatic retry; correct the config |
import_failed | Every processed unit failed | No automatic retry; check the receipt of automation run |
Schedule ticks and hook events never reset a pending backoff, and never clear a failure marked "no automatic retry". Once the automatic retries of a busy or runtime_error failure are used up, the next scheduled tick or hook event starts a fresh retry budget. A failure that needs correction stays blocked until you act: sessions automation run, set, or enable clear it.
A trigger that could not be admitted (lock not acquired in time, disk full, archive directory removed) is reported as not accepted and leaves no pending marker. An accepted trigger is never dropped silently: it stays pending until a run consumes it, and a trigger that arrives while a run is in progress starts another run afterwards. Imports are idempotent, so a rerun after a crash creates no duplicate turns and never changes provenance.
$A sessions automation disable claude # pause everything
$A sessions automation disable claude --hook # only the hook
$A sessions automation disable claude --schedule # only the schedule
$A sessions automation remove claude # uninstall and delete the profiledisable switches the trigger off first, then removes the owned hook entry and clears pending work admitted by that trigger. A run already in progress finishes its bounded batch; nothing new starts.remove uninstalls the owned hook entry and deletes the profile and its run state. While a run is in progress it refuses with SESSIONS_BUSY and changes nothing, so the run stays visible: pause first, then remove once status shows the run finished. It fails, and keeps the profile, if the Claude Code settings file cannot be read, so the entry never outlives the profile that can remove it.Automation creates no tasks, reminders, notifications, or OS services, promotes nothing to remembered facts, and makes no network calls.
Everything stays on your machine. New writes happen only in the archive root you chose (archive files, checkpoint state, and automation run state in .gno-sessions/automation.json), the archive config (including its sessions.automation block), and the named archive index. When you enable the Claude Code hook, GNO also edits one owned entry in the Claude Code settings file and keeps the previous file as settings.json.bak. Automation makes no network calls. Collection egress policy, local_only by default, applies to archive collections like any other.
Redaction runs before anything is persisted: archive text, titles, labels, metadata, locators, receipts, and diagnostics. It replaces common credential shapes with markers such as [REDACTED:api-key]: AWS key IDs, GitHub tokens, sk- style API keys, Stripe and Slack tokens, Google API keys, JWTs, bearer tokens, credentials in URLs, password= or api_key: style assignments, and private key blocks. A value detected once is redacted everywhere in that thread. Add your own exact strings (at least 4 characters) to the archive config; a change takes effect on the next import, which re-renders units whose source still exists and rescans archives whose source is gone in place:
sessions:
redaction:
literals:
- internal.example.corp
- my-project-codenameRedaction is best effort. It does not promise to catch every secret or personal detail in free text. Treat the archive as sensitive, keep it local_only unless you have reviewed it, and review before mixing it into a curated index.
Remote REST and MCP errors never include host paths: a filesystem or index failure surfaces as SESSIONS_RUNTIME_FAILURE with a fixed message.
Web UI — start a server on the archive pair and open /sessions to manage sources and the archive destination, preview a dry run (redactions, destination collections), import manually with partial-outcome details, and search sessions with harness, project, and role filters and explicit Human and Assistant badges. Only one server or daemon may own a GNO data directory, so while your curated server runs, give the archive server its own GNO_DATA_DIR and a free port. Normal curated servers do not attach the archive.
The Automation panel shows each profile off by default, separate hook and schedule switches (each confirmed after a preview of sources, destinations, the settings file, and the daemon prerequisite), daemon availability, pending, running, partial, and failed state, last success, Run now, Pause, and Remove. Run now works for any allowed client; profile changes, Pause, and Remove are same-host only. While a run is in progress the page refreshes its status every 2 seconds, so the profile shows running, and after each action keyboard focus moves to the resulting status, switch, or error. Status requests to a gno serve that is itself running an import can take several seconds. The server itself never drains pending work or runs schedules.
gno --config ~/gno-sessions/archive.yml --index sessions serve --port 3100MCP — a server started on the archive pair offers gno_sessions_status (read, including automation status), gno_sessions_import, and gno_sessions_automation_run (write tools, requiring --enable-write; without it they are not advertised, like the other write tools) in the full profile only, not core. Import takes sourceId, optional dryRun, and limit; any other key, such as paths, is rejected. The run tool takes a configured profileId only. There is no MCP discovery tool, so remote clients never learn host directories, and hooks and schedules cannot be enabled over MCP.
REST — on a server started with the archive pair: GET /api/sessions/status and POST /api/sessions/import with {sourceId, dryRun?, limit?}; paths are rejected. The import runs in a separate process, so the server keeps answering health, status, and Web UI requests while it runs. POST /api/sessions/automation/run with {profileId} is open to any allowed client too, and runs its imports the same way. GET /api/sessions/discover, POST /api/sessions/sources, DELETE /api/sessions/sources/:id, POST /api/sessions/init, and every automation profile change (PUT and DELETE /api/sessions/automation/:id, GET …/preview, POST …/enable, POST …/disable) answer only a same-host client (a local process; cross-origin browser pages are refused) and return 403 otherwise. Errors carry the specific code in details.sessionsCode; see Error codes.
SDK — open a client on the archive pair:
import { createGnoClient } from "@gmickel/gno"
const client = await createGnoClient({
configPath: "/absolute/path/to/archive.yml",
indexName: "sessions",
})
const preview = await client.importSessions({ sourceId: "codex", dryRun: true })
const receipt = await client.importSessions({ sourceId: "codex" })
const status = await client.sessionsStatus()
// Run a configured automation profile now (hooks and schedules are enabled
// from the CLI or a same-host client only).
const run = await client.runSessionsAutomation({ profileId: "claude" })
await client.close()client.discoverSessions() previews local roots, and importSessions also accepts { paths, collection, format? } for explicit files. status.automation carries daemon state and per-profile hook and schedule status; the SDK can run a configured profile but cannot switch hooks or schedules on. Errors are GnoSdkError with code VALIDATION or RUNTIME and the sessions code in details.code.
gno sessions automation run <profile>.gno sessions automation enable <profile> --hook claude-code again; it repairs only its own entry.| Code | Meaning | CLI exit | HTTP | REST / SDK kind |
|---|---|---|---|---|
SESSIONS_NOT_CONFIGURED | The config has no sessions block | 1 | 400 | VALIDATION |
SESSIONS_BINDING_MISMATCH | Archive config and index used apart | 1 | 400 | VALIDATION |
SESSIONS_SELECTION_REQUIRED, SESSIONS_DESTINATION_REQUIRED | No source or paths selected; path import without a collection | 1 | 400 | VALIDATION |
SESSIONS_UNKNOWN_SOURCE, SESSIONS_UNKNOWN_COLLECTION | Unregistered source ID or archive collection | 1 | 400 | VALIDATION |
SESSIONS_UNKNOWN_PROFILE | Unknown automation profile ID | 1 | 400 | VALIDATION |
SESSIONS_UNSUPPORTED_INTEGRATION | Hook for a harness without a verified integration | 1 | 400 | VALIDATION |
SESSIONS_UNSAFE_PATH, SESSIONS_UNSUPPORTED_FORMAT, SESSIONS_INVALID_INPUT | Unsafe path, unknown harness, or malformed input | 1 | 400 | VALIDATION |
SESSIONS_SOURCE_UNAVAILABLE | Source path missing or unreadable (no host path when remote) | 2 | 500 | RUNTIME |
SESSIONS_BUSY | Another import holds the archive lock | 4 | 409 | BUSY / RUNTIME |
SESSIONS_RUNTIME_FAILURE | Filesystem or index failure on REST/MCP (fixed, path-free message) | — | 500 | RUNTIME |
An import whose status is failed exits 2, except a selection with no supported session format, which exits 1 with SESSIONS_UNSUPPORTED_FORMAT. An automation run whose outcome is failed, and a hook that was not accepted, also exit 2. The CLI JSON envelope and REST errors carry the code in details.sessionsCode; MCP returns it in structuredContent.error; the SDK puts it in details.code. A same-host refusal on REST is 403 FORBIDDEN without a sessions code.