Problems and questions
It will not start
zettcode: Config key ... must be str / Unknown config keys ... — the message names the key that is wrong. Typos are rejected rather than ignored, so the fix is usually the spelling. Configuration has the full list.
zettcode: No models configured — ~/.zettcode/config.toml needs at least one [[models]] table. Start from the example in Getting started.
zettcode: no session <id> in <path> — --resume was given an id that this workspace's store does not hold. Ids are per workspace: check -w points at the same directory the session was created in, or run zettcode and use /resume to pick from the list.
It starts but nothing works
The first request fails with a connection error. The base_url is the OpenAI-compatible API root, not the chat website or full completion route. DeepSeek uses https://api.deepseek.com; other endpoints may require /v1. Use the connection test with the same model and key, and check API balance, permissions, and proxy settings.
The model name is rejected. Use the id the endpoint expects, not the name you see in a UI — display_model is what the header shows, and it is free text.
It is slow to show the first frame. The model client is created lazily, but local config, storage, and plugin loading still happen at startup. Try time zettcode --dry-run on macOS/Linux, or Measure-Command { zettcode --dry-run } in PowerShell, to measure the local startup path. It paints a frame and exits; it does not test the API connection. Compare with third-party plugins disabled before attributing the delay to the model or terminal.
The screen
Colours are wrong, or text is invisible. Run /theme light or /theme dark. At startup ZettCode asks the terminal for its background colour; a terminal that does not answer, a multiplexer in between, or TERM lying about its colour depth can all mislead it. /theme overrides that for the session, and ~/.zettcode/theme.toml overrides it for good.
The display is garbled after a while. Ctrl-L repaints from scratch. Resizing the window is handled, but a program that wrote to the terminal behind ZettCode's back cannot be.
Copying does not work in my terminal. ZettCode copies the selection itself when you release the drag, so the terminal's own selection is bypassed. If you prefer the terminal's, hold its selection modifier (Shift or Option) while dragging.
A pasted image does nothing. Image paste needs the desktop clipboard, so it works on macOS and on Linux with wl-paste or xclip installed, and is not available on Windows. A terminal that pastes an image as text (VS Code's, for instance) is recognised anyway.
Sessions
I lost a conversation. Everything is appended to ~/.zettcode/sessions/<workspace key>/<session id>/data.jsonl as it happens, so it is still there: /resume lists what this workspace has, newest first. The same folder's metadata.jsonl holds the titles.
A session does not resume the same way. Titles and token totals come from the metadata index; the conversation comes from the JSONL tree. If the last line of the file was cut off by a crash, that one line is ignored — the rest still loads.
The conversation is not what I sent. The model sees what it saw; the screen shows what you typed. @skill and image chips are expanded for the request and kept as chips for you, which is why a resumed session shows the chip.
Context and cost
ctx is high and rising. Open /context: the percentages are shares of what the window is holding, so the biggest row is what is filling it — usually tool output. /compact summarizes now instead of waiting for the trigger.
The cache hit rate is low. The provider caches a prefix, so anything that changes early in the request breaks it — a different system prompt, a different set of tools, a different model. Model switches, compaction, and changed instructions can lower it, and cache lifetime depends on the provider. There is no universal expected percentage. See the calculation and examples.
A command ran that I did not want. Review any previous approval choices: a grants a wider allowance, while p remembers an exact command for the current process. Restarting clears that memory. This approval applies to the built-in shell tool, not every file-editing or MCP tool. ZettCode does not sandbox tools; use version control and review the diff.
Skills, MCP, and plugins
My skill does not appear. It needs a directory with a SKILL.md whose front matter has name and description, under ~/.zettcode/skills/ or a configured [skills] roots. Only the front matter is indexed, and a name declared twice belongs to the earlier root.
An MCP server does not start. Its banner and errors go to ~/.zettcode/log/tui.log, because writing them on screen would corrupt the frame. A server that fails is reported by name in the transcript; a URL that is wrong or unreachable is the usual cause.
A plugin was skipped. Import it in a plain Python shell to see the error; ZettCode reports the plugin name and keeps the session going. Two plugins cannot share a name, and none can take a command name that already exists.
New versions
It offered me an upgrade. Once a day ZettCode asks PyPI, in the background, whether a newer release exists. No start waits for that request, and [update] enabled = false turns it off entirely. When the answer names a newer version, the next start shows a panel over the conversation:
- Upgrade now runs the upgrade for the install it came from —
uv tool upgrade zettcode, orpip install --upgrade zettcode— in the background. The window keeps the code it started with, so restart to use the new version. - Skip this version remembers it in
~/.zettcode/update.json. That one is never offered again; a later release is. - Not now (or
Esc) writes nothing, so the next start asks again about the same version.
The offer comes from that file, never from the network, so a machine that is offline simply hears nothing about versions.
Leaving
How do I exit? /quit, or Ctrl-D on an empty composer. Ctrl-C stops the running request, and on an empty request clears the draft — it does not quit. On exit the shell prints the zettcode --resume … line for the session you were in.