Hubzoid
Reference

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-fetch

Doctor 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:

StatusMarkMeaning
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" }
    }
  ]
}
FieldMeaning
formatOutput format version. Hubzoid 1.0.1 writes 1.
hubAbsolute path of the Hub checked.
hubzoidInstalled Hubzoid version. Absent when the Hub folder does not exist.
okfalse when any check has status fail.
checks[].idStable check id.
checks[].statusok, info, warn or fail.
checks[].summaryOne line for people.
checks[].detailA 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.

IdStatusesAppears
hub.dirfail--json only, when the Hub folder is missing
hub.agents_mdok, failalways
hub.envok, infoalways
secrets.deploymentok, info, failwhen a deployment secret is named (AWS_SECRET_NAME)
secrets.hubok, info, failwhen a Hub secret is named (HUBZOID_HUB_SECRET_NAME)
secrets.restrictedok, info, failwhen a restricted secret is named (HUBZOID_RESTRICTED_SECRET_NAME)
secrets.nameswarnwhen a secret name is set where it is ignored
config.layersinfowhen a file or secret sets any key
runtime.buildok, failalways
runtime.codex_loginok, failwhen the agent builds on codex-local
schedule.tasksok, failwhen schedule/ has tasks or invalid files
workflows.definitionsok, failwhen workflows/ has workflows
access.restrictedinfo, failwhen restricted/ has tools
identity.resolverinfo, failwhen identity/ has a roster
deps.versionsinfoalways
deps.sqliteok, failwhen the workflow engine uses SQLite
db.operationalok, info, warn, failalways
db.hubok, info, warn, failalways
auth.bridge_keysok, warn, failalways
auth.chat_signinok, info, failalways
auth.google_mergeok, warnwhen GOOGLE_CLIENT_ID is set
exposure.bindok, warnalways
model.credentialsok, info, warn, failalways
backup.ageok, warnwhen the operational store exists
scheduler.healthok, info, warn, failwhen the operational store exists and the Hub has tasks or workflows
db.readfailwhen 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.md or 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.

StatusSummaryWhat to do
okSchema at <revision>Nothing.
infoNot created yet; created at first startNothing. The database appears when the Hub first starts.
infoUnversioned; upgraded to <revision> at next startNothing. The next start upgrades it.
warnSchema at <old>; upgraded to <new> at next startTake a backup with hubzoid backup, then start the Hub to upgrade.
failSchema at unknown revision ...The database was written by a newer Hubzoid. Upgrade Hubzoid, or restore a backup taken before the newer version ran.
failDatabase is misconfigured: ...A database variable disagrees with the registered gateway manifest. Fix the gateway's configuration rather than one bridge.
failDatabase 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. dev is a public default, so anyone who reaches the bridge could use it. Set one or more random keys, for example from openssl rand -hex 32. hubzoid init writes 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_AUTH is on and its configuration is valid.
  • Fail when WEBUI_AUTH is on and WEBUI_SECRET_KEY is unset or still the Open WebUI default. Set a random value of 32 or more characters.
  • Fail when WEBUI_AUTH is on and an OAuth client id (GOOGLE_CLIENT_ID, MICROSOFT_CLIENT_ID, GITHUB_CLIENT_ID or OAUTH_CLIENT_ID) is set without WEBUI_URL. Sign-in callbacks are built from WEBUI_URL.
  • Fail when sign-in is off and HUBZOID_HOST exposes 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 when ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN is set, info when the claude CLI is on the PATH (check its login with claude /status), fail when there is neither. On a server, set CLAUDE_CODE_OAUTH_TOKEN.
  • For codex-local: the CLI and its login are checked by runtime.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:

StatusSummaryWhat to do
warnNew scheduled runs are held by a backup in progressWait for the backup to finish. The hold lifts on its own.
failWorkflow dispatch failed: ...The workflow engine could not start or load workflows at the last start. Fix the error, then restart the Hub.
warnNo 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> pausedSome tasks or workflows are paused. The detail lists them. Resume with hubzoid schedule resume.
okScheduled work is not held or pausedNothing.

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 doctor after installing, after every upgrade and after hubzoid restore.
  • In a gateway, run it for each Hub. The operational store and backup checks read the store the whole gateway shares.
  • Wire --json into monitoring and alert on ok: false, or on specific ids such as scheduler.health and backup.age.

Next steps

Read this page as Markdown

Choose which cookies Hubzoid can use. You can change this at any time from Cookie settings in the footer. Read the Cookie Policy for details.