Configuration
tapesctl has one configurable setting today — the server it talks to — and
one file to hold it. This page covers how that value is resolved, what else
lives beside it, and where diagnostics go.
Resolving the server URL
Section titled “Resolving the server URL”Four sources, consulted in this order:
1. --tapes-url on the command line (leaf position beats global position)2. TAPES_URL in the environment3. tapes-url in ~/.tapes/config.toml4. nothing → an error, never a guessed hostWith none of them:
tapesctl: no tapes server URL: pass --tapes-url, set TAPES_URL, or configure a default with `tapesctl config set tapes-url <url>`That refusal is deliberate. A capture pointed at whatever happened to be listening on a guessed port is worse than one that did not start.
There is no project-local layer. No .tapesrc, no directory walk, no
per-repository override. One user-level file, one variable, one flag.
Three mechanics that are easy to get wrong:
- Leaf position beats global position.
tapesctl --tapes-url A sessions list --tapes-url BusesB. - The configured value is installed as the flag’s default, rather than
resolved by hand. So precedence is the argument parser’s own, and a default
does not count as a user-supplied argument — which is why a bare
tapesctlstill prints help on a configured machine instead of complaining about a missing subcommand. - The global
--tapes-urldeliberately carries no environment binding. The parser counts an environment-sourced value as user-supplied, so bindingTAPES_URLat the top level would make a baretapesctlanswererror: requires a subcommandon any machine with the variable exported. The per-command declarations carry the binding instead, so the fallback still works everywhere it matters.
Cassette discovery resolves the same three sources itself, because it runs before arguments are parsed. See Cassettes.
One default, two ports
Section titled “One default, two ports”A tapes deployment serves reads and ingest on separate listeners, and the configured default holds a single URL. On a machine that both captures and reads a local server, configuration alone cannot name both.
Configure the one you type least often, and pass the other explicitly:
tapesctl config set tapes-url http://localhost:8081 # reads, the common casetapesctl start claude --tapes-url http://localhost:8082 # ingest, when capturingSee The two ports for which command is on which side.
config.toml
Section titled “config.toml”The path is ~/.tapes/config.toml, resolved once and nowhere else. Ask for it
rather than assuming:
tapesctl config path/Users/you/.tapes/config.tomlconfig path prints the path whether or not the file exists.
The schema is one key:
tapes-url = "http://localhost:8081"| key | type | meaning | validation |
|---|---|---|---|
tapes-url |
string | the server every command falls back to | must parse as a URL and use scheme http or https |
Setting it:
tapesctl config set tapes-url http://localhost:8081Validation happens at write time rather than on every command afterwards, and a rejected value writes nothing:
tapesctl: unknown config key "tapes-erl" (known keys: tapes-url)tapesctl: invalid tapes URLtapesctl: tapes-url must be an http or https URL; "ftp" is not a scheme this client can callThe file is deliberately not under $XDG_CONFIG_HOME. It sits beside
~/.tapes/logs, ~/.tapes/skills, and ~/.tapes/codex-app so there is one
directory to inspect, back up, or delete.
Rules that are invisible from the help text
Section titled “Rules that are invisible from the help text”- Unknown keys are preserved, not refused. Reading ignores them, and
writing edits the TOML document in place rather than re-serializing it — so
comments, ordering, your formatting, and keys a newer
tapesctlwrote all survive aconfig set. - A malformed file fails only the
configcommands. They surface a parse error; every other command loads with a fallback, warns at-v, and continues with an empty configuration. config setnever reads before it writes, so it can repair a known key holding a wrong-typed value. Structurally broken TOML is still refused rather than clobbered.config getcan print nothing from a file that is not empty. Only keys that are both known and set are listed. A file containing only a key this build has never heard of produces empty output — the forward-compatibility rule working as designed, and indistinguishable from an empty file.- A known-but-unset key prints nothing and exits
0, so$(tapesctl config get tapes-url)is empty rather than an error a script has to special-case.
Logging
Section titled “Logging”One rule governs everything here: while a harness holds the terminal, nothing may reach stdout or stderr. A stray log line lands in the middle of a TUI frame.
So diagnostics go to a file when, and only when, the command hands over the
terminal and verbosity is at its default. In practice that is start without
-v. Every other command — sync, capture, the read commands — logs to
stderr as usual.
~/.tapes/logs/start-YYYYMMDD-HHMMSS-<pid>.logFiles are created 0600 and appended to, never truncated. The path is printed
before the harness launches and again when it exits.
Pass -v to start to stream to stderr instead of a file, accepting what that
does to the display. That is the documented way to watch a capture live.
Level precedence is RUST_LOG, then the -v count, then info. A set-but-empty
RUST_LOG is treated as unset. An unparseable one prints
tapesctl: ignoring invalid RUST_LOG <directive> (<err>) to stderr and falls
back.
There is no stderr fallback when the log file cannot be opened. The run
prints tapesctl: diagnostics disabled — no log file (<err>) once, then
discards events. A corrupted TUI is judged more costly than a lost debugging
session.
Files and directories
Section titled “Files and directories”What tapesctl writes:
| path | written by |
|---|---|
~/.tapes/config.toml |
config set |
~/.tapes/logs/start-*.log |
start, at default verbosity |
~/.tapes/skills/<name>.md |
skill generate |
~/.tapes/codex-app/handoff.json |
plugin install codex-app |
~/.tapes/codex-app/plugin/ |
plugin install codex-app |
~/.agents/skills/, ./.agents/skills/, ~/.claude/skills/, ./.claude/skills/ |
skill sync |
~/.pi/agent/extensions/tapes-gateway.ts |
plugin install pi |
~/.config/opencode/plugins/tapes-gateway.ts |
plugin install opencode |
~/.codex/config.toml (or $CODEX_HOME/config.toml) |
plugin install/uninstall codex-app, patched in place |
<platform cache>/tapesctl/cassettes/<key>.json |
cassettes, help, and bare invocations — the only shapes that run cassette discovery |
Skill documents, log files, and installed plugin files are written 0600.
What it reads but never writes:
| path | read by |
|---|---|
~/.claude/projects/ |
the transcript tailer and sync |
~/.claude/sessions/<pid>.json |
Claude attribution |
$CODEX_HOME/sessions, or ~/.codex/sessions |
Codex attribution |
Environment variables
Section titled “Environment variables”| variable | read by |
|---|---|
TAPES_URL |
start, capture, sync, every read command, cassette discovery |
TAPES_UPSTREAM |
start, capture |
TAPES_WEB_URL |
start, capture |
TAPES_ORG_ID |
start, capture |
TAPES_AUTH_SUBJECT |
start, capture, sync |
RUST_LOG |
logging, all commands |
TAPESCTL_CACHE_DIR |
the cassette surface cache |
CODEX_HOME |
plugin install/uninstall codex-app, capture codex-app, start codex |
USER, then USERNAME |
the default --auth-subject: local:<user>, else local:unknown |
OPENAI_API_KEY |
start codex upstream selection; skill generate |
ANTHROPIC_API_KEY |
skill generate |
There is no telemetry variable, because there is no telemetry. tapesctl
reports nothing about you anywhere.
The parent environment is inherited wholesale by a launched harness.
start clears nothing, so a variable set in your shell reaches the harness
unchanged.
Commands that ignore --tapes-url
Section titled “Commands that ignore --tapes-url”Because the global flag propagates into every leaf’s help, --tapes-url is
rendered for commands that never make an HTTP call: config set, config get,
config path, skill list, skill sync, version, and plugin uninstall.
It is inert in all of them.
The reverse is worth stating too: plugin install and plugin uninstall write
local files from bytes the binary already carries and fetch nothing; skill sync is a pure local copy; skill list reads a directory.