Troubleshooting
Ordered by how often they bite and how long they take to diagnose.
A capture ran, but the session is not there
Section titled “A capture ran, but the session is not there”Symptom. tapesctl start finished without complaint and exited 0, and
tapesctl sessions list does not show the session.
First check the exit line. start says which of two things happened:
tapesctl: no turns were capturedThat means nothing landed in the server. It is not the same as
tapesctl: captured session …, which means it did.
The usual cause is the wrong port. Ingest and reads are separate listeners
— 8082 and 8081 on a local tapes serve. start, capture, and sync
all want ingest. Pointed at the read port, every turn is posted to a route
that does not exist there, every post is rejected, nothing is counted, and the
command still exits 0, because a telemetry failure is never allowed to take
your harness down.
Confirm it in the log file, whose path start prints twice:
grep "ingest rejected the turn" ~/.tapes/logs/start-*.logThat message is logged at warning level, so it is present at default verbosity. Then re-run against ingest:
tapesctl start claude --tapes-url http://localhost:8082Other causes, in the order worth checking:
- The call was never eligible. Only these path suffixes are captured:
/v1/chat/completions,/v1/responses,/codex/responses,/v1/messages,/api/chat. Only HTTP200— not any 2xx. Onlygzip,x-gzip, andzstdcontent encodings;branddeflatedrop the turn. These drops are logged atdebug, so they are invisible until you re-run with-v. - The harness never called a model. Launched and quit produces exactly the same line, honestly.
- You are reading a different server than you wrote to. With
TAPES_URLexported, a--tapes-urlon the read command overrides it, and vice versa — check both.tapesctl config get tapes-urlshows the configured fallback.
sessions get 404s the id that start printed
Section titled “sessions get 404s the id that start printed”Symptom.
tapesctl: tapes API returned 404 for http://localhost:8081/v1/sessions/f47ac10b-58cc-4372-a567-0e02b2c3d479: {"error": "session not found", ...}Cause. The id start prints is the harness’s session id. Read commands
take the tapes session id. They are different values in different
namespaces, and the console link start builds uses the harness one too. This
is a live defect, not a mistake on your part.
Workaround. Pass the printed id to sessions list as the
--harness-session-id filter — paired with --harness-id, the harness you
launched, because the server accepts the harness filter only whole — and read
the id on the result:
tapesctl sessions list --harness-id claude \ --harness-session-id f47ac10b-58cc-4372-a567-0e02b2c3d479 \ --tapes-url http://localhost:8081 | jq -r '.items[].id'01JDQ8F3K2M4N6P8R0T2V4X6Z8Use that id with every read command. The filter is the read API’s own
harness_session_id parameter, applied server-side; a printed id that matches
nothing returns an empty items. A lone half of the pair fails at parse with
the missing half named — that is tapesctl refusing a shape the server would
400.
If the printed id looks like the wrong session entirely, it may be: the id
start prints is whichever attributed turn ingest accepts first, so a
subagent’s turn landing ahead of the main thread’s names the sub-thread.
Listing is reliable where the printed line is not. See
Session ids.
no tapes server URL
Section titled “no tapes server URL”Symptom.
tapesctl: no tapes server URL: pass --tapes-url, set TAPES_URL, or configure a default with `tapesctl config set tapes-url <url>`Cause. None of the three sources named a server. tapesctl never guesses a
host — a capture pointed at whatever happened to be listening is worse than one
that did not start.
Fix. Any of the three. The third is the one worth doing:
tapesctl config set tapes-url http://localhost:8081If you set it and still get this, check which one you set and which one the
command wants. Remember a configured default holds one URL while the deployment
has two ports; capture commands may still need an explicit
--tapes-url http://localhost:8082.
If config get prints nothing but the file is not empty, that is expected:
only keys that are both known and set are listed. A file containing only keys
this build does not know produces empty output. tapesctl config path shows
where to look.
could not reach the tapes API: could not reach the tapes API
Section titled “could not reach the tapes API: could not reach the tapes API”The doubled clause is real, not a copy-paste error in this page.
Cause. The URL resolved, but nothing answered — wrong port, server down, or a host that does not route. Check the server is up and that you named the listener you meant.
Note that a path prefix in the URL is discarded:
--tapes-url http://host/base/ reads from http://host/v1/sessions, not
/base/v1/sessions. If your deployment is mounted under a path, that is a
server-side routing question, not something the flag can express.
pi cannot be captured until its capture plugin is installed
Section titled “pi cannot be captured until its capture plugin is installed”Symptom.
tapesctl: pi cannot be captured until its capture plugin is installed: no plugin at ~/.pi/agent/extensions/tapes-gateway.ts. Run `tapesctl plugin install pi` first.Cause and fix. Exactly what it says, and the check runs before anything binds or spawns, so nothing was half-started:
tapesctl plugin install piA pi session that runs but records nothing is a different problem: pass
--provider and --model together or not at all. They are pi’s own flags, so
they follow --. Given only one, pi ignores it and falls back to a saved
default that may be a provider this capture does not front.
tapesctl start pi --tapes-url http://localhost:8082 -- --provider anthropic --model <model-id>Plugin and extension conflicts
Section titled “Plugin and extension conflicts”Two capture clients tailing one transcript tree. Only one should. Pass
--no-transcripts to whichever should stand down — that is the case the flag
exists for.
A stale plugin copy under another name. pi loads every file in its
extension directory into one process, so a superseded copy is a second reader
contending over the same launch nonce. plugin install removes superseded
copies before renaming the new one into place and says so:
tapesctl: removed superseded <path>If you copied a gateway file by hand, remove your copy.
plugin install refuses a flag. --port and --codex-auth apply only to
hook-plugin harnesses. On a file-copy harness they are refused rather than
ignored, because a flag that silently does nothing reads exactly like a flag
that worked:
tapesctl: --port does not apply to pi, whose capture plugin is a file copyThe same rule governs --schema on claude and codex.
plugin install claude “does nothing”. It reports that no plugin is needed
and exits 0. That is the ordinary answer, not a failure — claude and
codex are captured by redirection.
codex-app is still registered after uninstall. plugin uninstall codex-app removes the provider entry and the state directory, but leaves the
plugin registered with Codex; its --help line about “any configuration it
wrote” overstates what happens. The command prints the remaining step:
codex plugin remove tapesctl-codex-app@tapesctlcapture codex-app refuses to start. Either the handoff is missing —
tapesctl: could not read the codex-app handoff at ~/.tapes/codex-app/handoff.json: run `tapesctl plugin install codex-app`— or the handoff and the app’s own configuration disagree about the address, in
which case capture refuses rather than warns. A capture bound to one address
while the app talks to another would run perfectly and record nothing.
Subagent work renders as flat text
Section titled “Subagent work renders as flat text”Cause. The transcript lane is missing. It is the only source of a session’s
causal skeleton — which Task call forked which subagent — and the wire lane
cannot recover it, because on the wire a subagent’s calls are the same shape of
request as the main thread’s.
Either the harness has no transcript lane at all (pi and opencode do not),
or --no-transcripts was passed, or nothing was tailing when the session ran.
For a Claude session that already ended, sweep it up:
tapesctl sync --tapes-url http://localhost:8082 --since-days 0sync is safe to repeat — the server dedups on a content hash.
sync says it swept less than expected
Section titled “sync says it swept less than expected”--since-days defaults to 7, and --help does not say so. A tree older
than a week is silently partially swept. Use --since-days 0 for everything;
the window is a cost bound, never a correctness one.
sync files Claude sessions only. The harness id it stamps is hardcoded,
so --projects-root pointed at a Codex or pi tree will not do what the flag
name suggests.
sync exited 1 but the summary looked fine. Any undelivered transcript
fails the command, deliberately, because sync is an explicit request to move
data. The summary prints first and everything that landed is durable — re-run
to retry the rest.
search returns 503
Section titled “search returns 503”The deployment has no span embeddings. The response body says which of the two causes it is: no embedder or store configured, or no embedding pass has run. It surfaces as:
tapes API returned 503 for …: <body>An empty result set is not an error — that prints No results found. and exits
0.
capture will not tell me how much it captured
Section titled “capture will not tell me how much it captured”It cannot. Unlike start, capture prints no turn counts and no unattributed
warning, because its tally is never drained. It reports that a session began
and, at the end, how many sessions it saw. To confirm what landed, read the
server.
start exited 0 but my harness failed
Section titled “start exited 0 but my harness failed”start does not propagate the harness’s exit status. A non-zero child is
warned about and start still exits 0. CI wrappers that treat start as
transparent to exit status need to check the harness themselves.
Which build am I running?
Section titled “Which build am I running?”You cannot tell from the binary. tapesctl version and tapesctl --version
both report 0.1.0 for every release to date, because the crate version has
never been bumped while releases are tagged independently. Treat 0.1.0 in a
bug report as version-less, and record where you got the binary instead.
Turning on more detail
Section titled “Turning on more detail”tapesctl -v sessions list --tapes-url http://localhost:8081 # debugtapesctl -vv sessions list --tapes-url http://localhost:8081 # traceRUST_LOG=debug tapesctl sessions list --tapes-url http://localhost:8081RUST_LOG overrides the -v count. A set-but-empty RUST_LOG is treated as
unset; an unparseable one warns and falls back.
For start, -v also changes where logs go: at default verbosity it writes
to ~/.tapes/logs/start-*.log because the harness owns the terminal, and -v
streams to stderr instead. If you see
tapesctl: diagnostics disabled — no log file (<err>)the file could not be opened and events are being discarded — there is no
stderr fallback, because a corrupted display is judged worse than a lost
debugging session. Re-run with -v to get the events on stderr.