Troubleshooting
Where Polytoken keeps its files
Section titled “Where Polytoken keeps its files”Polytoken keeps its own state in three directories. The defaults follow the XDG Base Directory specification and are the same on every platform, including macOS.
| Contents | Default location | Redirect with |
|---|---|---|
| Configuration file, skills, facets, subagents, hooks, themes | $XDG_CONFIG_HOME/polytoken (default: ~/.config/polytoken) | POLYTOKEN_CONFIG_PATH |
| Sessions, logs, provider login state | $XDG_DATA_HOME/polytoken (default: ~/.local/share/polytoken) | POLYTOKEN_DATA_PATH |
| Model catalogs discovered from dynamic providers | $XDG_CACHE_HOME/polytoken (default: ~/.cache/polytoken) | POLYTOKEN_CACHE_PATH |
A redirect variable names the directory itself: Polytoken uses the value
exactly as written and does not append polytoken. With
POLYTOKEN_DATA_PATH=/tmp/pt, a new session lands in
/tmp/pt/sessions-v1/<id>. An unset or empty variable falls back to the
matching $XDG_* value, then to the default inside your home directory.
If POLYTOKEN_CACHE_PATH, XDG_CACHE_HOME, and HOME are all unset or
empty, Polytoken cannot resolve the cache directory for dynamic provider
catalogs. Polytoken then skips caching, logs a warning, and still discovers
the catalogs live from the provider.
The .polytoken/ directory at the root of a project is a different thing from
the global config directory. It holds project-scoped configuration only; see
Checking your configuration.
Reading the logs
Section titled “Reading the logs”The daemon’s log, the interface logs, and crash logs all land in
$XDG_DATA_HOME/polytoken/logs/ (default: ~/.local/share/polytoken/logs/).
ls -lt ~/.local/share/polytoken/logs/ | headThe path in this example is the default location; substitute it when you have
set $XDG_DATA_HOME.
Run logs carry a timestamp and a process ID, so each run gets its own file:
2026-09-05T13-22-04Z-368502.logis the daemon’s log for the run with process ID 368502. The daemon writes structured JSON entries to this file. A daemon that runs in the background also redirects its stdout and stderr into the same file, so startup failures and panics appear alongside the JSON entries.- The conversation interface writes logs ending in
-tui.log; the configurator writes logs ending in-configui.log. - A crash log ends in
.crash.logand marks an involuntary exit. The file records the session ID, the exit reason, and, for panics, a backtrace. Normal exits write no crash log. When a panic or daemon attachment lease loss writes a crash log, the TUI announces the file path after restoring the terminal. The lease-loss detach message prints even when no crash log was written, without the path.
Two controls set verbosity, one per process. For the daemon’s log, set
daemon.log_level; its values are error, warn, info (the default),
debug, and trace. For the interface logs, set RUST_LOG to a standard
tracing filter, for example RUST_LOG=debug; the default is info. Polytoken
reports an error instead of launching when RUST_LOG holds an invalid filter.
Polytoken does not delete old log files. Runs accumulate until you remove the files yourself.
For non-interactive runs, polytoken exec --print-session-logs mirrors the
session’s JSONL events and the run’s trace log to stderr while the run
executes. That trace log, trace.jsonl, lives in the session directory rather
than the logs directory. See CLI.
Inspecting a session on disk
Section titled “Inspecting a session on disk”Each session owns a directory named by its session ID. Current sessions live
under $XDG_DATA_HOME/polytoken/sessions-v1/, sessions created by older
releases live under $XDG_DATA_HOME/polytoken/sessions/, and migration
artifacts live under $XDG_DATA_HOME/polytoken/sessions-migration/. Three
files answer most questions:
log.jsonlholds the session’s event log: every prompt, reply, tool call, and marker, in order. Polytoken replays this file to rebuild the conversation when you resume. See Sessions.session.jsonholds the metadata the session picker reads, including the project directory, the title, and timestamps. It may also containusage_tracking, which identifies sessions created by this version, andfirst_user_activity_at, the time of the session’s first user turn. Older sidecars omit both fields.record.jsonis the ownership record the daemon writes while it owns the session; it records the daemon’s port and process ID. After an abnormal exit, a stalerecord.jsoncan remain. While listing live sessions,polytoken sessionsre-checks each recorded process and removes the records it confirms are stale. If Polytoken cannot verify a recorded process, it keeps the record and logs a warning. The record must stay in its own session’s directory: if you move or edit arecord.jsonso its contents name a different session, commands that dispatch on the record (continue, terminate, reap) reject it with an error instead of trusting the misplaced payload. Restore the record to its own session directory to fix this.
The session directory also holds shell-job output, subagent transcripts, and
the exec trace log for the session; todo records live in a todos/
subdirectory. When daemon.logging.persist_system_prompts is on, Polytoken
also creates system-prompts/ and stores one 0600 text sidecar for each
changed composed system prompt. Polytoken keeps the directory at mode 0700.
The sidecars help you compare provider prompts after a run and do not
participate in session replay or exports.
Polytoken stores full output for capped tool results in the session’s tool-results/ directory and sets each spill file to mode 0600.
Native context records and exported artifacts
Section titled “Native context records and exported artifacts”A native compaction boundary stays in log.jsonl as a complete record.
Polytoken records each conversion from a native context to portable
text the same way. Each record is self-contained: Polytoken can resume the
session, measure the context, and convert it without rebuilding provider state
from files that can change. A crash before Polytoken writes the record leaves the
previous context active. A crash after Polytoken writes the record lets Polytoken
resume from it after a restart. Subagents whose models use native compaction
keep the same kind of records in their own subagent-*.jsonl transcripts,
under the same rules.
The conversation interface shows which compaction method ran, whether Polytoken fell back to text compaction, how full the context is, and timing details that contain no secrets, but never the provider’s continuation state. Feedback artifact exports redact native records, while the on-disk session log keeps everything Polytoken needs to resume. Treat the raw session directory as sensitive.
polytoken sessions lists live sessions and, as a side effect, removes
registry entries it confirms are stale. Add --all to list every session directory on disk, including
finished ones; the --all listing is read-only and skips the stale-entry
cleanup. Legacy sessions that predate session.json appear in the --all
listing but cannot be resumed.
When you resume a large legacy session, Polytoken shows live migration progress while it converts the session. Migration can take several minutes for a session with extensive history or many subagent logs. You can interrupt the resume safely. A later resume retries the conversion and converges on the migrated session.
Conflicting session trees
Section titled “Conflicting session trees”A conflicting-session-tree error means Polytoken found both the legacy session directory and the migrated session directory for one session ID. Polytoken does not choose one tree automatically because the two trees may contain different history.
Polytoken keeps the original legacy tree in sessions-migration/<id>/legacy-backup while migration remains recoverable. When a recreated legacy directory contains only startup scaffolding, Polytoken moves it to sessions-migration/<id>/recreated-stub/ so a later retry can run without deleting the scaffolding. A legacy directory containing log.jsonl is never scaffolding.
To recover, stop any process that owns the session and resume the session again. Another resume converges automatically when the legacy directory lacks log.jsonl or migration artifacts remain: Polytoken archives the scaffolding stub and either publishes the staged history or restores the backup for another retry. When both the legacy log.jsonl and a valid migrated session directory contain history, no retry can resolve the conflict. Polytoken refuses to choose either tree and preserves both. Keep both trees unchanged and seek assistance.
Repairing out-of-order session history
Section titled “Repairing out-of-order session history”Use polytoken sessions repair <session-id> when Polytoken warns that v1
history is corrupt and a session fails to load, resume, or hand off, but every
line in log.jsonl remains valid JSON. This command repairs records whose
indexes no longer follow their physical order. It does not change record
content, and it saves the original log beside log.jsonl as
log.jsonl.corrupt-<UTC-timestamp> before replacing the log.
Stop the daemon that owns the session before running the command. Polytoken refuses to repair a session with a live owner or an owner record that it cannot read or verify. Run:
polytoken sessions repair <session-id>The command refuses a missing session, a legacy session, any unparseable or torn line, and timestamps that move backward. Those problems are not index-order corruption and need a different recovery path. Polytoken validates the repaired log before reporting success; if the log is already valid, the command leaves it unchanged.
With --sessions-dir <dir>, Polytoken accepts either the sessions root the
daemon uses or the versioned <name>-v1 directory beside it, and refuses a
copied or moved v1 directory whose sibling directories are missing. The
refusal names both accepted locations. Repair requires the session
storage lock. That lock lives beside the v1 data, so a relocated copy cannot
be repaired in place.
Checking your configuration
Section titled “Checking your configuration”Polytoken looks for a file named config.json, config.json5, config.toml,
config.yaml, or config.yml in the global config directory and in
.polytoken/ at the root of your project. A directory may hold exactly one of
them; two produce a load error naming both files. Project values override
global values, and a null in the project layer deletes a global key. Lists
merge instead of replacing: the project layer appends its items to the global
list, and an entry written as !item removes the matching item inherited from
the global list.
Some keys belong to the global layer only; data, telemetry, updates, and
daemon.discovery are the common ones, and the configuration
reference notes which blocks carry the same
restriction. A project layer that sets a global-only key fails the load.
models and defaults follow the same rule when a global layer also exists.
Model-group definitions use a different precedence rule: user/global
definitions replace project definitions, and project definitions replace
shipped definitions. A member of a model group cannot be null. Omit
modelgroups.<name> to remove that layer’s override and restore the
lower-priority definition.
polytoken config validatechecks the merged result. On success, the first line readsmerged config valid. Add--useror--projectto check one layer alone; the first line then readsuser config valid: <path>orproject config valid: <path>and names the directory Polytoken checked. The second line starts withactive_session.default_facet (info):and reports the effective merged value when you check the merged result, or the value you configured in the selected layer. When the key is unset, the second line explains that startup falls back toexecute.polytoken config editopens the project layer in your editor by default, or the user layer with--user, and validates before saving: the real file changes only after validation passes.polytoken config uiopens the interactive configurator.
String values in the file can reference an environment variable with ${VAR}
or $VAR, the common way to supply an API key. A referenced variable that is
not set fails the load, and polytoken config validate names it. Write
${VAR:-default} to use the default text when the variable is unset or empty,
and write $$ for a literal $. Command substitution, $(command), is a
separate global-layer feature that stays off until you set
config_command_substitution; see the configuration
reference.
polytoken doctor runs the same startup checks the daemon runs and prints
each result as a checklist line. The checks cover the startup surface:
configuration, dynamic models, model groups, providers and models, permissions,
shell behavior, hooks, skills, facets, subagents, tools, and MCP servers. The
model-groups check names skipped candidates when an authored route is degraded,
and reports the published-route count when every route is healthy. The command
exits non-zero when a check fails; MCP connection failures and partial model
groups appear as warnings. It starts no session and writes no session record.
The dynamic-models check can refresh the cached provider catalogs, the same
cache a session launch refreshes.
When a model-group warning appears, review its group and skipped leaves. Daemon logs also emit a WARN for each degraded or withheld group during startup and a successful reload. An authored group whose every leaf is confirmed unavailable prevents config loading, and the load error names each unavailable leaf. When every leaf is skipped because a dynamic provider’s catalog is still pending, Polytoken withholds the group with a warning instead. Check provider configuration, model names, and dynamic provider availability before trying the session again.
A provider reports model not found
Section titled “A provider reports model not found”A “model not found” error does not always mean the model name is wrong. Some providers, Google AI Studio among them, return this same error when your API key or project is not allowed to use a model that exists. When the provider explains the refusal in its own response, Polytoken shows that message with the error, so read it before editing your configuration. If the message names your key or project, the fix is on the provider’s console side: check the model’s availability for your account tier, or switch to a model your credentials can serve.
Environment variables
Section titled “Environment variables”Polytoken reads these variables when it starts.
| Variable | Effect |
|---|---|
POLYTOKEN_CONFIG_PATH | Uses this directory as the config root, without appending polytoken. |
POLYTOKEN_DATA_PATH | Uses this directory as the data root, without appending polytoken. |
POLYTOKEN_CACHE_PATH | Uses this directory as the cache root, without appending polytoken. |
XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME | Standard XDG locations, consulted when the matching POLYTOKEN_*_PATH variable is unset. |
RUST_LOG | Verbosity of the interface log files; the default is info. |
EDITOR | Selects the editor for polytoken config edit; the default is vi. For the interface’s drop-to-editor command, a configured tui.editor key wins, then VISUAL, then EDITOR; the default is vim. |
POLYTOKEN_SKIP_UPDATE_CHECK | Set to any value to skip the launch-time update check. Polytoken sets this itself when it re-launches after an update. |
POLYTOKEN_DISABLE_FEEDBACK | Set to 1 to disable manual feedback, including /feedback and Ctrl+Shift+F. |
POLYTOKEN_FEEDBACK_DSN | Uses a custom destination for feedback reports instead of the default service. |
POLYTOKEN_TUI_COLOR_MODE | Forces truecolor or 256 output when terminal color detection is wrong. See Installation. |
POLYTOKEN_TUI_DISABLE_KEYBOARD_ENHANCEMENT | Set to 1 to skip requesting the terminal’s extended keyboard protocol. |
POLYTOKEN_DAEMON_CREDENTIAL_FILE | Environment form of the daemon’s --credential-file flag. See Daemon Authentication. |
POLYTOKEN_OUTPOST_URL | Default URL for Outpost management commands that accept --outpost-url. |
POLYTOKEN_OUTPOST_TOKEN | Token for Outpost commands and polytoken connect when --outpost-token is omitted. |
OTEL_* | OpenTelemetry exporter settings, when trace export is enabled. See Telemetry. |
Bash hook handlers receive their own set of POLYTOKEN_* variables describing
the session; see Hooks.
When a provider fails
Section titled “When a provider fails”Raise daemon.log_level to debug, reproduce the problem, and read the newest
daemon log.
For a durable record, turn on daemon.logging.provider_autopsy. Each provider
failure then produces a redacted report under autopsies/ inside the session
directory, without the request or response contents. The same directory also
holds diagnostics for tool calls that failed to parse, and those contain the
raw malformed input, so treat autopsies/ as sensitive. Polytoken prunes
older reports automatically.
Polytoken also writes a small summary line to the daemon log when each
provider request starts and finishes. Those lines carry counts and sizes
only, never request contents, and they are always on at the default log
level. For a detailed size and structure breakdown of every provider
request, including successful ones, turn on
daemon.logging.provider_request_diagnostics in your configuration file,
or use the Provider request diagnostics toggle in the configurator’s
Daemon pane. The key is off by default because the breakdown adds
preparation work to each request. Provider autopsy works independently of
this key: autopsy keeps gathering the detail failure reports need even
when the diagnostics key is off.
The /feedback command can attach the current TUI log tail and screenshots to a
report. Ctrl+Shift+F opens the same form. See
Crash Reporting and Feedback.
Goal Driver pauses after empty responses
Section titled “Goal Driver pauses after empty responses”When you set a goal, the Goal Driver keeps the work moving by sending continuation turns on its own. If a provider repeatedly returns empty responses, Polytoken pauses that automatic continuation and shows a reminder instead of asking the same model to continue. Check the provider or select another model before trying again. With a model group, Polytoken tries the next candidate after repeated empty responses; see Model groups and failover.
Use /goal resume to continue the goal with a fresh allowance of automatic
continuations. The same command works when the goal pauses at its continuation
limit.
The assistant stalls in extended reasoning
Section titled “The assistant stalls in extended reasoning”Some models reason for a long time before they produce an answer. When a response keeps producing reasoning without any visible text or tool call, Polytoken interrupts the stream, asks the model to answer or act, and retries the response. These interruptions happen silently in the conversation view. Polytoken records each reminder in session history and replays it to the model on the retry.
The retry sequence is scoped to a stretch without progress. When the model resumes writing text that actually says something, or calls a tool, Polytoken discards the accumulated retries and starts fresh, so earlier interruptions do not weigh on later productive work. Blank text does not count: a response that emits only whitespace between reasoning bursts never resets the count, so the retry sequence keeps climbing until the response produces real output or the turn ends. A prompt you send after an interruption also starts fresh.
The retries are also bounded for your whole turn. When the same turn keeps falling back into extended reasoning, Polytoken counts every interruption and ends the turn after a bounded number of them. From the second interruption onward you also see a warning card that reports how many times reasoning stalled in this turn, so a repeating stall is visible without opening the session log.
An interruption does not prove the model was repeating itself. Long, legitimate reasoning can trigger the interruption too. When retries still produce no answer or tool call, or when the turn reaches its interruption limit, Polytoken ends the turn with a visible note that says the response was stuck in a reasoning loop. Try sending the request again, or switch to a model that reasons less.
A response sits with no visible progress
Section titled “A response sits with no visible progress”Polytoken watches every response stream on the wall clock, for the main conversation and for subagents. If a stream stops making progress (for example after your computer sleeps and wakes), Polytoken reconnects it through the same bounded retry ladder it uses for ordinary stream interruptions. The retry salvages the response up to the last complete block and repeats the request; repeated consecutive stalls end the turn with a visible error.
In the main conversation, time spent running a tool and time spent in a provider rate-limit wait never count toward that ladder, so a long tool batch or a legitimate backoff does not trigger a reconnect. Subagents get the same protection during a provider rate-limit wait, so no stall marker appears while the backoff runs. A long subagent tool batch can still show the marker briefly.
While a response is in flight, the status card names what the model is waiting for, including how long since the provider last sent anything, so a dead stream is visible before recovery fires.
When you cancel a turn yourself and the turn had not produced any visible output yet, Polytoken appends a short muted note that says you canceled the turn before it produced any output, so the transcript never ends silently on your prompt.
Checking language servers, themes, and definitions
Section titled “Checking language servers, themes, and definitions”polytoken lsp checklists every language server Polytoken knows and whether its binary resolves on this machine. Naming a server also runs its--versionprobe and reports the outcome as an informational note; the exit code reflects PATH resolution only. See Language Servers.polytoken validate skill <target>,polytoken validate facet <target>, andpolytoken validate subagent <target>parse and validate one definition file, by name or by path, using the same logic the daemon runs at startup.polytoken theme validate <path>checks one theme file against the token catalog. With no path, the command checks the built-in default theme, and--allchecks every discovered theme. See Themes.polytoken vfs lsandpolytoken vfs catlist and print the resources Polytoken ships, such as the default theme. See The Polytoken VFS.
Tracing the language-server protocol
Section titled “Tracing the language-server protocol”When a language server misbehaves (missing diagnostics, wrong edits, a hang), you can record the raw protocol exchange between Polytoken and that server in a private per-session file:
-
Set
daemon.lsp.protocol_trace.modetoverbosein your global config file (~/.config/polytoken/config.yamlis the default location), and restrict capture with the allowlist:daemon:lsp:protocol_trace:mode: verboseservers: ["typescript-language-server"] -
Restart the session. Capture is scoped to session startup: a running session never starts or stops capturing from a config change.
-
Reproduce the problem, then inspect the trace file. Polytoken stores it next to the session’s other diagnostic files under
$XDG_DATA_HOME/polytoken(default~/.local/share/polytoken), named__lsp_<server>_<root>.protocol.jsonl, with owner-only file permissions. -
Turn the key back to
offand restart the session when you are done.
The trace can contain document text and file contents, so it is never sent
anywhere automatically. mode: messages records one line per protocol
message without content; verbose records full message content. Share only
an excerpt you have reviewed. This key exists only in configuration files:
it does not appear in the interactive configurator, and a project config
that sets it is rejected at load.