Repository navigation
Troubleshooting
Start with keryx doctor:
uv run keryx doctor # --json for machine-readable outputIt checks the configuration location, the migration status, the OpenAI key, the
coding-agent sign-ins, the Twilio settings and signature validation, PUBLIC_HOST, the
tunnel software, the caller allowlist, the PIN, the service manager, and whether
DATA_DIR is writable. It also checks that your secrets are readable only by you, stored
outside a Git working tree, and absent from config.toml. The --fix option only
tightens the permissions of secret files.
Further checks cover your name, the memory, PROJECTS_ROOT, Google, and each of the four
plugins (send_to_slack, check_email, check_billing, and cluster_stats). For each
plugin, it says whether it's on, off, or on but refused, and why. It also names any
settings left in config.toml from before the plugins existed. For a feature that's only
partly configured, doctor says what's missing.
The symbols mean:
-
✅— fine. -
○— optional, and not set up. -
⚠️— limits what Keryx can do. -
❌— won't work. Only❌makes the command exit non-zero.
It's safe to run before anything is configured, and it never prints a secret.
keryx setup reads the same checks to decide what to ask you.
| Thing | Where |
|---|---|
| Server log |
STATE_DIR/logs/keryx.log (rotated, 10 MB × 5) |
| Service stdout/stderr | STATE_DIR/logs/{keryx,cloudflared,ngrok}.{out,err}.log |
| The restart watchdog | STATE_DIR/logs/restart-watch.log |
| Voice transcripts | DATA_DIR/calls/<session_id>.log |
| Task progress | DATA_DIR/tasks/<id>.log |
| Written reports | DATA_DIR/tasks/<id>.md |
| Task rows |
DATA_DIR/tasks.db (or just keryx tasks show <id>) |
| Plugins and your own tools |
DATA_DIR/tools/ (or keryx plugins and keryx tools) |
| The last restart | uv run keryx restart --status |
Unless you've moved them, STATE_DIR is ~/.local/state/keryx and DATA_DIR is
~/.local/share/keryx. keryx config path prints every directory. See
Data and Retention.
On Linux, journalctl --user -u keryx also shows the unit's own view.
-
The call connects but nobody speaks — the media socket was rejected. Check that the caller is in
ALLOWED_CALLERS, and that the webhook URL matchesPUBLIC_HOSTexactly. -
Twilio shows 403 — signature validation failed. The tunnel host and the configured webhook URL don't match.
-
Tasks fail instantly — the coding agent isn't signed in, or its SDK isn't installed.
keryx auth statusandkeryx doctorsay which. To sign in, runkeryx auth login claude(orcodex). -
A task with no project ran in
DATA_DIR/workspace, not among your repositories —PROJECTS_ROOTdoesn't exist on this machine. It defaults to~/projects, and Keryx never creates it. Set it to where your code lives.keryx doctorsays which directory it found. -
A task under the service can't find a tool that works in your terminal (
npx,conda,cargo…) — the service runs with thePATHof the shell you installed it from. From a shell that has the tool, runscripts/install-systemd.shorscripts/install-launchd.shagain. See Running as a Service. -
No text ever arrives — texting is off by default. If your Twilio account can send SMS to your number, run
keryx config set SMS_ENABLED true. Calls work either way. -
Keryx calls you "the owner", or talks as if it has never met you —
OWNER_NAMEis blank, or there's no memory yet. The About you section ofkeryx setupsets both. You can also runkeryx config set OWNER_NAME …andkeryx memory seed; see Command Line Reference. -
A project's brief never makes it into a call — briefs are capped at 1,500 characters each and 6,000 in total. A brief that would go past the total is left out entirely, and the log names it:
left N project brief(s) out of the prompt, past 6000 characters in all: …. Shorten the briefs. Projects listed inPROJECTSare included first. -
Slack, email, billing, or cluster stats is never offered — these four are plugins, and none is offered until you turn it on. Use
keryx setup→ Plugins, orkeryx plugins install NAME.keryx pluginsshows which are on. Each call reads the plugins again, so turning one on or editing its.tomldoesn't need a restart. -
keryx pluginssays a plugin isrefused— its files are there, but the next call wouldn't load it. The line under it says why:-
no bot token and channel(send_to_slack) — runkeryx config set SLACK_BOT_TOKEN --stdinand setchannel_idinsend_to_slack.toml. Alternatively, set anmcp_serverwhose config has both. -
not signed in to Gmailorthe claude CLI is not installed(check_email) — runkeryx auth login gmail, oruv sync --extra claude. -
[clusters] in cluster_stats.toml names no host, orthe guard … is not a file— add a host (keryx plugins hostslists the ones it could ask), or blankguardto use the built-in guard. -
does not parse, or a value that doesn't validate — fix the.tomlby hand, or walkkeryx setup→ Plugins again, which rewrites it. -
writable by other users (chmod go-w)orowned by another user— someone else could have written the file, orDATA_DIR/toolsitself, so Keryx refuses it without reading it. Make it yours and runchmod go-won it.
When a plugin it just turned on is refused,
keryx plugins installsays the same and exits 1. It never takes a secret on its command line. Instead, it points you tokeryx config set KEY --stdinorkeryx auth login gmail. -
-
keryx plugins installsaysonly the owner may install a plugin— it ran inside a Keryx task. Only you, at your own terminal, can install or remove plugins. -
cluster_statssays the login has expired — it only asks over an ssh ControlMaster that you already have open, and never opens a connection of its own. To bring the master up, log in to that host in a terminal.keryx plugins hostsshows which masters are up now. -
A tool of your own is never offered —
keryx toolslists whatDATA_DIR/toolsholds, and why a file is refused. A file is refused if it won't import, defines no tool, takes a built-in tool's name, or is writable by someone else. The command exits 1 while any file is refused. A file whose name starts with_is skipped on purpose. See Custom Tools. -
A setting you changed has no effect —
keryx config listshows where each value comes from. The process environment, such as a unit'sEnvironment=, wins over the store. -
Work is refused on the phone — you haven't given the PIN on this call. Before the PIN, a call can hear what Keryx knows, but can't act on it. See Security Model.
-
A call opens without telling you what finished — either there's no PIN yet, or
BRIEFING_BEFORE_PINis off. Until a PIN exists, nothing of yours is read out: set one inkeryx setup, or key one in on the first call. WithBRIEFING_BEFORE_PINoff, the results follow once you give the PIN. -
"PIN entry is locked for now", and then it hangs up — wrong PINs across calls reached
PIN_FAILURE_LIMIT(10 in 24 hours by default). PIN entry is locked forPIN_LOCKOUT_MINUTES(an hour), and until then the right PIN is refused too. If this wasn't you, someone is guessing: change the PIN inkeryx setup. To clear the lock early, stop Keryx, deleteDATA_DIR/pin-failures.json, and start Keryx again. The log line to look for isPIN entry is locked on every call until …. -
"Sorry, this line is busy" —
MAX_PHONE_SESSIONScalls (two by default) were already in progress, or had just been answered. A dropped call can take a few seconds to close. Wait a moment and call again. -
keryx servesaysDEBUG_SKIP_TWILIO_VALIDATION is on while PUBLIC_HOST is set— that switch is only for a machine that nothing outside can reach, so it can't be combined with a tunnel. Turn it off:keryx config set DEBUG_SKIP_TWILIO_VALIDATION false. -
The approval bridge never rings — check
uv run keryx approvalsfirst. If a prompt you left waiting doesn't appear there asraised, Keryx decided it wasn't eligible. The server log says why, on a line startingnot escalating a … prompt:.-
the approval hook that sent it does not say whether it trimmed the request— the hook in~/.claude/hooks/is older than Keryx. Runscripts/install-claude-hook.shagain. -
the hook had to trim it— the command, or the content of the file write, is longer than 4096 characters. This is deliberate, and the prompt waits on screen. -
is not an option a keypad may approve,is not the name of a remote,the command is not on the shell allowlist,means something to the shell,cannot be said whole,it writes into .git— all deliberate. See The Approval Bridge for what the policy allows, and why.
-
-
Google tools fail in a task — the stored credentials may have expired. Run
keryx auth login google-workspaceagain. This applies only whenGOOGLE_WORKSPACE_MCPis on; see Google Workspace MCP. -
Keryx cuts you off while you're thinking — that's turn detection, not a fault.
VAD_EAGERNESS=lowis more patient than the default,medium. To go back to a fixed timer that you can tune directly, setVAD_MODE=serverandVAD_SILENCE_MS. -
"Restart yourself" refuses, or
keryx restartsays there's no installed service — nothing supervises the process that was asked. Akeryx servestarted in a terminal can't restart itself, even where the service is installed too.keryx doctorsays which unit it found. See Running as a Service. -
A restart "failed" with exit code -15 — it didn't.
systemctlruns inside the service it restarts, so the restart stops it too. See Restarting Keryx. -
The service never came back, and nothing told you — check
systemctl --user status keryxandSTATE_DIR/logs/keryx.log.uv run keryx restart --statusshows what the logs said at the time.
- Command Line Reference — every command, and which ones work while the server is down.
- Running as a Service — the units, and where each one writes.
User documentation for Keryx.
For the threat model, see SECURITY.md;
for settings, the coding agents, and the tools, see docs/.
Apache-2.0, Copyright 2026 William Koch.
Setting up
Running it
Security and data
Tools and advanced features