Doctor checks
What hubzoid doctor checks, its text and JSON output, every stable check id with its possible statuses, and how to fix each failure, including configuration secrets.
hubzoid doctor checks a Hub and the deployment around it: files, configuration layers and secrets, the agent build, schedules and workflows, database schemas, sign-in, network exposure, model credentials, backups and scheduled work. It only reads. It never creates a database, runs a migration or starts the workflow engine, so it is safe to run against a live deployment. It reports setting names and where they come from, never their values.
hubzoid doctor ./ops-desk
hubzoid doctor ./ops-desk --json
hubzoid doctor ./ops-desk --skip-secret-fetchDoctor loads the Hub's .env into its own process before it checks anything, the same way the Hub does when it starts. Run it with the environment the Hub runs with, for example as the service user on the server, so checks such as auth.bridge_keys see the real values. When the configuration names AWS Secrets Manager secrets, doctor reads each one once to prove it is reachable. --skip-secret-fetch lists them without calling AWS, and the other checks then use the files alone.
Output
Each check has a status:
| Status | Mark | Meaning |
|---|---|---|
ok | ✓ | Fine. |
info | · | Worth knowing. Nothing to fix. |
warn | ! | Works, but needs attention. |
fail | ✗ | Will not work, or is unsafe. |
The text output prints one line per check with its id in brackets, and lists the details under any warn or fail. Under config.layers it lists each configured key with its layer and source:
✓ AGENTS.md present (hub.agents_md)
✓ .env present (hub.env)
· 2 configured key(s): 2 from the hub (config.layers)
BRIDGE_API_KEYS: hub (/srv/hubs/ops-desk/.env)
MODEL: hub (/srv/hubs/ops-desk/.env)
✓ Agent builds: 'ops-desk' via ClaudeRuntime (runtime.build)
✓ 2 enabled task(s) (schedule.tasks)
· hubzoid 1.0.1 (deps.versions)
✓ SQLite 3.46.1 (deps.sqlite)
✓ Schema at op_0007 (db.operational)
✓ Schema at hub_0001 (db.hub)
✓ 1 bridge key(s) set (auth.bridge_keys)
· Chat app sign-in is off (fine for local use only) (auth.chat_signin)
✓ The public port listens on 127.0.0.1; bridges stay on 127.0.0.1 (exposure.bind)
· claude-local: uses the local `claude` login (check it with `claude /status`) (model.credentials)
! No backup recorded. Run `hubzoid backup`. (backup.age)
✓ Scheduled work is not held or paused (scheduler.health)Exit codes: 0 when no check fails, 1 when any check fails, 2 when the Hub folder does not exist. Warnings do not change the exit code.
JSON format
--json prints one object. Check ids are only ever added, never renamed, so scripts and monitoring can key on them.
{
"format": 1,
"hub": "/srv/hubs/ops-desk",
"hubzoid": "1.0.1",
"ok": false,
"checks": [
{
"id": "auth.bridge_keys",
"status": "fail",
"summary": "BRIDGE_API_KEYS includes the public key 'dev'",
"detail": null
},
{
"id": "backup.age",
"status": "ok",
"summary": "Last backup 1.2 days ago",
"detail": { "at": 1790000000.0, "path": "/srv/backups/hubzoid-backup-20260924-020000.tar.gz", "by": "cli:hubzoid@ops-server" }
}
]
}| Field | Meaning |
|---|---|
format | Output format version. Hubzoid 1.0.1 writes 1. |
hub | Absolute path of the Hub checked. |
hubzoid | Installed Hubzoid version. Absent when the Hub folder does not exist. |
ok | false when any check has status fail. |
checks[].id | Stable check id. |
checks[].status | ok, info, warn or fail. |
checks[].summary | One line for people. |
checks[].detail | A list, an object or null, depending on the check. |
When the Hub folder is missing, the output holds a single hub.dir check with status fail.
A monitoring job can alert on failures only:
hubzoid doctor /srv/hubs/ops-desk --json | jq -r '.checks[] | select(.status == "fail") | .id'Checks
Checks run in the order below. Some appear only when the thing they check exists, as noted.
| Id | Statuses | Appears |
|---|---|---|
hub.dir | fail | --json only, when the Hub folder is missing |
hub.agents_md | ok, fail | always |
hub.env | ok, info | always |
secrets.deployment | ok, info, fail | when a deployment secret is named (AWS_SECRET_NAME) |
secrets.hub | ok, info, fail | when a Hub secret is named (HUBZOID_HUB_SECRET_NAME) |
secrets.restricted | ok, info, fail | when a restricted secret is named (HUBZOID_RESTRICTED_SECRET_NAME) |
secrets.names | warn | when a secret name is set where it is ignored |
config.layers | info | when a file or secret sets any key |
runtime.build | ok, fail | always |
runtime.codex_login | ok, fail | when the agent builds on codex-local |
schedule.tasks | ok, fail | when schedule/ has tasks or invalid files |
workflows.definitions | ok, fail | when workflows/ has workflows |
access.restricted | info, fail | when restricted/ has tools |
identity.resolver | info, fail | when identity/ has a roster |
deps.versions | info | always |
deps.sqlite | ok, fail | when the workflow engine uses SQLite |
db.operational | ok, info, warn, fail | always |
db.hub | ok, info, warn, fail | always |
auth.bridge_keys | ok, warn, fail | always |
auth.chat_signin | ok, info, fail | always |
auth.google_merge | ok, warn | when GOOGLE_CLIENT_ID is set |
exposure.bind | ok, warn | always |
model.credentials | ok, info, warn, fail | always |
backup.age | ok, warn | when the operational store exists |
scheduler.health | ok, info, warn, fail | when the operational store exists and the Hub has tasks or workflows |
db.read | fail | when the operational store cannot be read |
hub.agents_md
Whether AGENTS.md exists at the Hub root. Fail: create it, or run hubzoid init to scaffold a Hub. Nothing else works without it.
hub.env
Whether the Hub has a .env. Info when it does not, which is normal when settings come from the process environment (a systemd unit or a container).
secrets.deployment, secrets.hub and secrets.restricted
One check for each AWS Secrets Manager secret the configuration names. See configuration secrets.
- Ok when the secret can be read, with the number of keys it holds. The detail names the secret, its region and where the name was set, never a value.
- Info with
--skip-secret-fetch, which lists the secret without reading it. - Fail when it cannot be read. The summary names the secret, the layer and the AWS error class. Check the name, the region and the credentials of the account that runs the Hub. The other checks then use the files alone.
secrets.names
Warn when a secret name is set where Hubzoid ignores it: AWS_SECRET_NAME in the .env of a Hub that belongs to a gateway, HUBZOID_HUB_SECRET_NAME anywhere but the Hub's .env, or HUBZOID_RESTRICTED_SECRET_NAME anywhere but restricted/.env. Move the name to the place the message gives.
config.layers
Info listing each key a file or secret sets, with the layer it comes from (hub, hub secret, restricted, restricted secret or deployment secret), its source and any lower layer it overrides. Names only, never values. Use it to see which value wins.
runtime.build
Builds the agent exactly as the bridge would, and reports the runtime it chose: OpenAIAgentsRuntime, ClaudeRuntime or CodexRuntime. Fail shows the error. Common causes:
- Invalid frontmatter in
AGENTS.mdor a sub-agent, or a file with no body. - A model id whose provider key is missing, for example
MODEL is set to 'openrouter/...' but OPENROUTER_API_KEY is not. - A skill file that cannot be parsed, or invalid JSON in
connectors/.mcp.json.
Fix the file named in the message, then confirm with hubzoid test.
runtime.codex_login
Appears when the agent builds on codex-local. Ok when Codex CLI 0.147.0 is installed and its file-backed login works. Fail otherwise: install @openai/codex@0.147.0 and run codex -c 'cli_auth_credentials_store="file"' login as the account that runs the Hub. The check reads the login status only and makes no model call. See agents and models.
schedule.tasks
Parses every file in schedule/. Ok reports the number of enabled tasks and lists all task names. Fail lists each invalid file with the reason: a missing trigger (schedule: or on_webhook:), both triggers at once, a cron that never matches, an empty body on an agent task, push: true without commit:, or a limit that is not a positive integer. Invalid tasks are skipped at runtime while valid ones keep firing. hubzoid schedule list shows the same problems. See markdown tasks.
workflows.definitions
Loads the code workflows under workflows/. Ok lists each one with its schedule (or manual) and timezone. Fail lists source: error for each workflow that does not load. While a workflow module is broken, code workflows are off for that start, and markdown tasks keep running. See code workflows.
access.restricted
Imports restricted/*.py. Info lists the permissions the restricted tools require. Fail means a file does not import. Fix the Python error it names. See restrict tools.
identity.resolver
Loads identity/access.py or identity/access.csv. Info confirms a roster is present. Fail means the roster raised an error while loading, most often an exception in access.py. See Hub files.
deps.versions
Info with the installed versions of hubzoid, open-webui, dbos, litellm, openai-agents, claude-agent-sdk, alembic, sqlalchemy, casbin and fastmcp. A missing package shows null. Include this detail when you report a problem.
deps.sqlite
Appears when the workflow engine's database is SQLite. Ok reports the SQLite version. Fail means Python 3.12 or newer is linked against SQLite older than 3.42, which the workflow engine cannot use, so scheduled tasks and workflows cannot run. Use a Python build with a newer SQLite (python.org, Homebrew, Debian 13 or Ubuntu 24.04) or PostgreSQL. The Docker image already ships a newer SQLite.
db.operational and db.hub
The schema version of the operational store (access, identities, audit, usage) and of the Hub's own database.
| Status | Summary | What to do |
|---|---|---|
| ok | Schema at <revision> | Nothing. |
| info | Not created yet; created at first start | Nothing. The database appears when the Hub first starts. |
| info | Unversioned; upgraded to <revision> at next start | Nothing. The next start upgrades it. |
| warn | Schema at <old>; upgraded to <new> at next start | Take a backup with hubzoid backup, then start the Hub to upgrade. |
| fail | Schema at unknown revision ... | The database was written by a newer Hubzoid. Upgrade Hubzoid, or restore a backup taken before the newer version ran. |
| fail | Database is misconfigured: ... | A database variable disagrees with the registered gateway manifest. Fix the gateway's configuration rather than one bridge. |
| fail | Database unreachable: ... | Check the URL, network and credentials of the database. |
See upgrading.
auth.bridge_keys
Checks BRIDGE_API_KEYS.
- Fail when it is unset or includes
dev.devis a public default, so anyone who reaches the bridge could use it. Set one or more random keys, for example fromopenssl rand -hex 32.hubzoid initwrites a random key into a new Hub's.env. - Warn when a key is shorter than 16 characters.
auth.chat_signin
Checks web chat sign-in.
- Ok when
WEBUI_AUTHis on and its configuration is valid. - Fail when
WEBUI_AUTHis on andWEBUI_SECRET_KEYis unset or still the Open WebUI default. Set a random value of 32 or more characters. - Fail when
WEBUI_AUTHis on and an OAuth client id (GOOGLE_CLIENT_ID,MICROSOFT_CLIENT_ID,GITHUB_CLIENT_IDorOAUTH_CLIENT_ID) is set withoutWEBUI_URL. Sign-in callbacks are built fromWEBUI_URL. - Fail when sign-in is off and
HUBZOID_HOSTexposes the port (0.0.0.0,::or empty). Turn sign-in on before you expose the port. - Info when sign-in is off and the port stays on loopback, which is fine on your own machine.
See authentication.
auth.google_merge
Appears when GOOGLE_CLIENT_ID is set. Ok when OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true, so Google sign-in attaches to an account created in the Console. Warn otherwise, because people with a Console-created account cannot then sign in with Google. Set OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true.
exposure.bind
Where the public port listens. Warn when HUBZOID_HOST is 0.0.0.0, :: or empty: put TLS in front of the port, for example with a reverse proxy. Ok otherwise. The detail confirms that bridges stay on 127.0.0.1.
model.credentials
Whether the Hub's model has credentials in the environment.
- For
claude-local: ok whenANTHROPIC_API_KEYorCLAUDE_CODE_OAUTH_TOKENis set, info when theclaudeCLI is on thePATH(check its login withclaude /status), fail when there is neither. On a server, setCLAUDE_CODE_OAUTH_TOKEN. - For
codex-local: the CLI and its login are checked byruntime.codex_login. - For any other model: ok when LiteLLM finds the provider's keys, fail listing the missing variables, warn when the check itself could not run.
See agents and models.
backup.age
When the last hubzoid backup of this deployment completed. Warn when no backup is recorded or the last one is more than 7 days old. Ok otherwise, with the backup record as detail. See backup and restore.
scheduler.health
The state of scheduled work, checked in this order:
| Status | Summary | What to do |
|---|---|---|
| warn | New scheduled runs are held by a backup in progress | Wait for the backup to finish. The hold lifts on its own. |
| fail | Workflow dispatch failed: ... | The workflow engine could not start or load workflows at the last start. Fix the error, then restart the Hub. |
| warn | No dispatcher heartbeat recently: ... | Code workflow dispatch is on but has not reported for over 150 seconds. The Hub is stopped or stuck. Check the process and its logs. |
| info | <n> paused | Some tasks or workflows are paused. The detail lists them. Resume with hubzoid schedule resume. |
| ok | Scheduled work is not held or paused | Nothing. |
See operating runs.
db.read
Fail when the operational store exists but cannot be read. The message names the error. Check the database file's permissions, or the database server.
Use it in operations
- Run
hubzoid doctorafter installing, after every upgrade and afterhubzoid restore. - In a gateway, run it for each Hub. The operational store and backup checks read the store the whole gateway shares.
- Wire
--jsoninto monitoring and alert onok: false, or on specific ids such asscheduler.healthandbackup.age.
Next steps
HTTP endpoints
Every HTTP route Hubzoid 1.0.1 serves on the edge, bridge, MCP server, Console API, artifact viewer and webhook receiver, with method, authentication and purpose.
Changelog
What changed in Hubzoid 1.0.1 for workflows and the accounts they run as, artifacts and email, the Admin Console, runtimes, personal MCP connections, operations and security.
