Operational Troubleshooting Guide
This guide indexes exact diagnostic messages printed by hale dna, hale habitat, and hale node, grouped by subsystem, along with their root cause and remediation steps.
- One-shot CLI verbs print errors prefixed with
hale dna: <reason>. - Running body processes prefix log lines with
hale dna run:orhale dna dev:, and fleet node agents prefix lines withhale node <name>:.
Four read-only commands work offline (even when no organism body is running) and are the primary starting point for diagnosing state:
hale dna status # status projection: organism lease, journal chain, reviews, body, memoryhale dna board # items awaiting Board action, missing vault secrets, and recent violationshale dna history t1 # causal event chain for a specific Task, Work, attempt, or Reviewhale dna secrets # required vault secret slots and whether each is populatedThe Record
Section titled “The Record”Nothing is running here
Section titled “Nothing is running here”organism: not running — reading the Journal- Cause:
hale dna statusprints this when no body process currently holds the record’s lease. Read-only commands (status,history,board,review <id>,ui) still function normally by reading the Postgres record directly. - When submitting tasks: If you run a command that waits for the organism to respond (such as
hale dna task create) while no body holds the record lease anywhere, the CLI refuses before writing:the organism is not running (no body holds the record's lease); start it with `hale dna run`, or ask with --no-wait to leave the ask in the record - Fix: Start a body (
hale dna devorhale dna run) in another terminal, or pass--no-waitto record the request in Postgres so the organism processes it the next time a body starts.
requested intent …, but the organism journaled no answer in time (record revision …)- Cause:
hale dna task createwrote the request row, but no admission or refusal row arrived before the timeout. The request remains in the record. - Fix: Verify that a body is running (
hale dna body) and inspect its terminal output for ano nerves:warning (The nerves)—a body disconnected from NATS JetStream cannot relay or receive requests.
No record, or a toolchain that moved
Section titled “No record, or a toolchain that moved”no record in memory for … (run `hale habitat graft`)- Cause: The directory contains DNA files, but its record schema does not exist in Postgres memory.
- Fix: For a new or unattached checkout, run
hale habitat graft . --attach. For a second clone of an existing project whose record identity is already pinned inhale.toml, runhale habitat migrate(orhale dna upgradewithHALE_DNA_MEMORY_DSN_OWNERset) to write the head DSN into.hale/dna/memory.env.
this clone holds a record an earlier toolchain kept on git (refs/dna/journal), and records are not carried forward: re-init a fresh record — delete the old refs (…), remove the `[dna] record` line from hale.toml if it has one, and run `hale habitat graft . --attach`- Cause:
hale habitat graftandhale dna upgradereject clones containing legacy Git-backed journal refs (refs/dna/journal). - Fix: Delete the legacy Git refs, remove
[dna] recordfromhale.tomlif present, and re-graft the directory:Terminal window git for-each-ref --format='%(refname)' refs/dna/ | xargs -n1 git update-ref -dhale habitat graft . --attach
… holds the head's DSN and is readable by others (mode …): `chmod 600 …`, or remove it and run `hale habitat migrate …`- Cause:
.hale/dna/memory.envcontains the Postgres head role DSN, and its file permissions allow group or world access. - Fix: Restrict permissions with
chmod 600 .hale/dna/memory.env(or delete the file and re-runhale habitat migrate).
embedded dna: … (hale …); vendor/dna was materialized from … — run `hale dna upgrade`- Cause: Your installed
halebinary embeds a newer DNA core digest than the one materialized invendor/dna/. - Fix: Run
hale dna upgradeto synchronizevendor/dna/andhale.lock.
The chain does not verify
Section titled “The chain does not verify”journal: … event(s), chain BROKEN at …- Cause: Every row in
record_rowshashes its predecessor (sha256(prev|kind|entity|body)). A reader detected a digest orprevmismatch, indicatingrecord_rowswas modified outside the append-onlyrecord_appendstored procedure. - Fix: Compare the affected rows against a database backup (Memory and the record).
A connection that cannot be made
Section titled “A connection that cannot be made”connect: … could not be read (…); nothing was changed- Cause: The target NATS URL is unreachable, or the peer record has not published its identity card to its stream.
- Fix: Verify network connectivity and run
hale dna nerves migrateon the target record to publish its card.
Memory (Postgres)
Section titled “Memory (Postgres)”Working memory and the record reside in Postgres (Memory and the record).
The ledger does not answer
Section titled “The ledger does not answer”memory: record + ledger (operations and leases live in memory; THE LEDGER IS UNREACHABLE (…): what is read here is the last projection, and nothing is admitted until it answers)- Cause: Postgres is unreachable.
hale dna statusfalls back to the last cached local projection, and all new task admissions and head writes are refused until Postgres responds. - Fix: Restore Postgres connectivity (or run
hale habitat migrateto start local habitat containers) and retry the command.
The body has no memory
Section titled “The body has no memory”hale dna run: no memory: HALE_DNA_MEMORY_DSN_SPINE is not set (`hale dna dev` hands the host the spine's DSN of memory that `hale habitat migrate` migrated, from HALE_DNA_MEMORY_DSN_OWNER or the organism's allocation in the local habitat); nothing is projected or admittedhale dna dev: the organism's memory allocation in the local habitat (memory-…) is not up: `hale habitat migrate …` brings it up and migrates it; or set HALE_DNA_MEMORY_DSN_OWNER to a Postgres of your ownhale dna dev: the organism has no record identity yet: its memory is the local habitat's allocation named for it (`hale habitat graft` pins one)…/.hale/dna/memory.env held a DSN derived from …, not from the organism's memory in the local habitat (…), and is removed: run `hale habitat migrate …`, which brings the organism's memory up and migrates ithale dna run: this record's operations live in the ledger (adopted at …), and no memory is named here (HALE_DNA_MEMORY_DSN_SPINE); a body without its memory admits nothing — set it, or run under `hale dna dev`- Cause:
- Under
hale dna run:HALE_DNA_MEMORY_DSN_SPINEis unset in the environment. - Under
hale dna dev: the organism’smemory-<id>container in the local habitat is stopped, or.hale/dna/memory.envpointed to a stale DSN.
- Under
- Fix: Run
hale habitat migrate(requiresdocker composeonPATH) to start and migrate the local habitat allocation, or exportHALE_DNA_MEMORY_DSN_SPINE(printed byhale dna memory migrate) when runninghale dna run.
The schema is another toolchain’s
Section titled “The schema is another toolchain’s”hale dna run: memory: memory schema … is at version …; this toolchain needs version …; run `hale habitat migrate <organism>` (with HALE_DNA_MEMORY_DSN_OWNER for a Postgres of your own)… memory schema … is at version …, newer than this toolchain's …; it is not migrated back- Cause: The Postgres schema version for this record is behind (or ahead of) the installed
halebinary. - Fix: If the schema is behind, run
hale habitat migrate <organism>(withHALE_DNA_MEMORY_DSN_OWNERset if using an external Postgres server). Schema migrations never downgrade; if the database schema is newer than your binary, upgradehale.
A context package would be stale
Section titled “A context package would be stale”memory's projection is at row …, before the record's knowledge row …; the spine applies it on its tickhale dna run: memory: projection waits for the record to be shared (the sync did not complete)- Cause: A worker requested a Work’s context package (
hat), but the Postgres knowledge graph projection was still behind a knowledge row in the record after waiting 20 seconds. - Fix: Ensure an organism body (
hale dna devorhale dna run) is running with database connectivity so its tick loop projects pending knowledge rows.
A write the ledger refused
Section titled “A write the ledger refused”hale dna: `… …` was not written to the ledger: …- Cause & Fix by Refusal Reason:
… retired from this organism (person.retired at row …): The specified user has been retired viahale dna retire.task … is handed to …, not to …: Only the assigned person can mark a handed Taskdone.fenced: the lease … is not held/… expired at … (epoch …) and was not renewed: The body lease fencing token is stale or expired.the role … does not write as …: a head writes as its owner's members: On a multi-owner shared record, the head’sHALE_DNA_MEMORY_DSN_HEADbelongs to a different organization than the caller.`…` is a row of the record, not the ledger: Internal routing mismatch;record_rowskinds cannot be inserted intoledger_rows.
Protected evidence
Section titled “Protected evidence”no receipt key: memory keeps no protected evidence until hale dna memory migrate with HALE_DNA_RECEIPT_KEYmemory already seals protected evidence under another receipt key; bodies sealed under it would no longer open, so it is not replacedreceipt file: … was redacted; a redacted body is not filed again… was redacted …; a redacted body is not kept again- Cause & Fix:
- Missing receipt key: Filing or reading
customerorconfidentialevidence requires an encryption key in Postgres memory. Runhale dna memory migratewithHALE_DNA_RECEIPT_KEYset (at least 16 characters). Once set, the key cannot be replaced with a different one. - Redacted body: Once a receipt digest is redacted via
hale dna receipt redact, its plaintext body can never be re-filed. receipt.withheldinstead ofreceipt.classified: Protected evidence was generated when no Postgres memory was configured; only the digest and class were recorded. Re-generate the evidence while connected to memory.
- Missing receipt key: Filing or reading
The nerves
Section titled “The nerves”The nerves are backed by NATS JetStream (The nerves).
The body hears nothing
Section titled “The body hears nothing”hale dna run: no nerves: HALE_DNA_NATS_URL_SPINE and HALE_DNA_NATS_ORG are not set (`hale dna dev` hands the host the spine's URL of nerves whose stream `hale habitat migrate` created, on HALE_DNA_NATS_URL_OWNER or the organism's allocation in the local habitat); the organization hears nothing the record askshale dna dev: the organism's nerves allocation in the local habitat is not up (`hale habitat migrate …` brings it up and creates the stream)nerves migrate: the server does not run JetStream (start it with -js)- Cause & Fix:
- Under
hale dna dev: runhale habitat migrateto start thenerves-<id>container and create the JetStream stream. - Under
hale dna run: exportHALE_DNA_NATS_URL_SPINEandHALE_DNA_NATS_ORG(printed byhale dna nerves migrate). - External NATS servers must be started with JetStream enabled (
nats-server -js).
- Under
The organization does not come to read
Section titled “The organization does not come to read”hale dna run: the organization did not come to read the nerves within 20s; the facts wait in the stream- Cause: The
dna/orgchild process failed to connect to NATS or lacks permission to consume its durable consumer (spine, orspine_<owner>on a shared record). - Fix: Inspect
.hale/dna/org.logand the NATS allocation’snats.conf. Queued messages remain safely in the JetStream stream until the connection succeeds.
A publish the stream did not take
Section titled “A publish the stream did not take”hale dna run: nerves: …; stopping for the unit to start this node again- Cause: NATS JetStream failed to acknowledge a published message before the timeout. The body logs
violation.recorded(adapter_undeliverable) and exits with code75. - Fix: Under a provisioned systemd service, the unit restarts the body automatically and replays unanswered requests. Under
hale dna dev, restart the command manually (and runhale dna nerves migrateif the stream was deleted).
The heart’s pull is refused
Section titled “The heart’s pull is refused”hale dna run: the heart's pull on its durable … was refused … time(s) while the connection held: the durable, or its stream, is gone; it is made again- Cause: The application heart’s durable consumer or stream was missing while the NATS connection stayed up.
- Fix: The body automatically recreates the durable consumer, logs
violation.recorded(pulse_stopped) once per outage, and continues. Inspect the latest 5 violations withhale dna board.
The Spine
Section titled “The Spine”See The spine for budgets, workflows, and leases.
The budget is spent
Section titled “The budget is spent”… budget exhausted (spent … of … micro-dollars this day in … call(s))- Cause: Cumulative model spend in the current budget window reached
allowance_micros. New tasks (hale dna task create), worker spend allowances (hale dna work allowance), and Leader Review evaluations (attempt.allowance_refusedonreview:<id>) are refused until the window resets. - Fix: Approve pending Reviews manually as the Board (
hale dna review <id> approve), wait for the next window, or increaseorg_budget()indna/org/models.hlvia a reviewed change:fn org_budget() -> dna::BudgetPolicy {return dna::BudgetPolicy { window: "day", allowance_micros: 25000000 };}
An intent noted and never re-offered
Section titled “An intent noted and never re-offered”offered before a restart and no Task was born; not re-admitted — ask again- Cause:
hale dna historyshowsintent.unrecoveredwhen an intent was offered immediately prior to a restart before atask.bornrow was committed. To prevent duplicate side effects, the spine never re-offers it automatically. - Fix: Re-run
hale dna task createif the work is still needed.
An effect whose outcome is unknown
Section titled “An effect whose outcome is unknown”- Cause: An attempt claimed by an
uncertainperformer failed to settle or let its lease expire (attempt.unresolved). Automated retries are halted because external tools may have already acted. - Fix: Inspect the attempt’s history and resolve it manually:
Terminal window hale dna effect resolve attempt:<id> --outcome ok|failed
The Heart and the Body
Section titled “The Heart and the Body”The body lease is held elsewhere
Section titled “The body lease is held elsewhere”hale dna run: a body for this record is live on … (ticked …, lease expires in …s); a record admits one body — `hale dna body claim --force` takes it over when that body is gonehale dna: body: live on …, ticked … ago; a record admits one body. `hale dna body claim --force` takes the lease from a body that is gone (it stops itself when it next asserts the lease)hale dna run: the body lease is no longer mine: held by …; a record admits one body — stoppinghale dna: body: the lease is held by …, not this clone (…); `hale dna body release --force` releases another'shale dna run: memory cannot be reached, so the body lease cannot be taken; a body runs only under a lease it can prove- Cause: Each record permits at most one active body process (enforced via a fenced lease in Postgres memory). Starting a second body exits with code
3. - Fix: Run
hale dna bodyto see which machine/clone holds the lease. If the previous body crashed or its host is gone, take over the lease with:(Or release it withTerminal window hale dna body claim --forcehale dna body release --force.) Any displaced body process automatically halts the next time it checks its lease token.
A change that did not survive
Section titled “A change that did not survive”- Rolled-back mutation (
mutation.rolled_back):hale dna historyshowsexpression.crashed(with exit code, fleet instance, and node) followed bymutation.rolled_back. The newly expressed application exited during the post-deploy observation window, so the host automatically rolled the genome back to the base revision. …: never expressed by …: A fleet instance failed to reportinstance.upat the new revision within the timeout. Check thehale node <name>terminal output for Git fetch or build errors.…: denied: candidate breaks the fleet: The candidate compiled individually, buthale fleet checkfailed a cross-service claim (mutation.denylists the failing step).mutation.refusedwithcandidate moved after review: … != …: The sandbox commit changed between Review approval and genome application. Re-submit the task to open a fresh Review.
No fleet
Section titled “No fleet”no `[dna] fleet = "<name>"` in hale.toml: the DNA expresses no fleet here- Cause:
hale dna fleet,hale dna deploy, andhale dna rollbackrequire a fleet seed name inhale.toml. - Fix: Add
[dna] fleet = "<name>"tohale.toml(The heart and the body).
Senses and Reflexes
Section titled “Senses and Reflexes”See Senses and reflexes.
hale dna senses up: the local habitat's senses' store runs under docker compose, which is not on PATHhale dna senses url: the organism's senses' store in the local habitat is not up (`hale habitat migrate …` brings it up)reflexes: HALE_DNA_NATS_URL_REFLEXES, HALE_DNA_NATS_VAULT_REFLEXES and HALE_DNA_NATS_ORG name the nerves this program fires ontoreflexes: no store (HALE_DNA_SENSES_URL); nothing to readreflexes: the vault holds no credential … (`hale dna upgrade` provisions the organism's secrets)- Cause & Fix:
- Start the local Prometheus-compatible senses store (
senses-<id>) withhale habitat migrateorhale dna senses up(requiresdocker composeonPATH). - When running reflexes standalone, export
HALE_DNA_NATS_URL_REFLEXES,HALE_DNA_NATS_VAULT_REFLEXES, andHALE_DNA_NATS_ORG(fromhale dna nerves migrate),HALE_DNA_SENSES_URL(fromhale dna senses up), and runhale dna upgradeif vault credentials are missing.
- Start the local Prometheus-compatible senses store (
The Skin (Vault and Secrets)
Section titled “The Skin (Vault and Secrets)”See The skin.
A secret is missing
Section titled “A secret is missing” MISSING forge-token the forge's token — a person supplies it: `hale dna secret set FORGE_TOKEN` MISSING model-OPENAI_API_KEY the model's key OPENAI_API_KEY — a person supplies it: `hale dna secret set OPENAI_API_KEY`body: no credential for the model (none of … is in the vault where the body runs); `hale dna secret set <NAME> [--body <user@host>]` puts one theresecret: the forge's token is not in the vault (forge-token); `hale dna secret set FORGE_TOKEN` fills it- Cause: Required vault slots are empty. Remember that model API keys are read strictly from vault slots (
model-<NAME>), never from environment variables. - Fix: Populate human-supplied secrets via
stdin(pass--body <user@host>for a remote body):For machine-generated secrets (Postgres roles, NATS accounts, loopback OIDC secrets), runTerminal window hale dna secret set OPENAI_API_KEYhale dna secret set FORGE_TOKENhale dna upgrade.
A secret the verb will not take
Section titled “A secret the verb will not take”secret set: `…` is no slot of the organism's (FORGE_TOKEN, OIDC_CLIENT_SECRET, or a credential dna/org/models.hl names: …); `hale dna secrets` lists themsecret set: the value never goes on the command line (it would sit in every shell history and process list); name the variable alone and give the value on stdinsecret rotate: … was never set (`hale dna secret set …` first)- Fix: Pass only a recognized slot name (
hale dna secretslists valid names), pipe or type the secret onstdin(never as a CLI argument), and runsecret setbeforesecret rotate.
A drawn secret is gone
Section titled “A drawn secret is gone”nerves migrate: the vault holds no credential for the nerves' … (`hale dna upgrade` provisions the organism's secrets; `hale dna secrets` lists them)the memory allocation's password is not provisioned (postgres-owner-… in the vault, and its file beside the allocation; `hale dna upgrade` provisions them, `hale dna secrets` lists them)- Fix: Run
hale dna upgradeto regenerate missing infrastructure passwords in the vault and the local habitat allocation directory (~/.local/share/hale/habitat/local/allocations/<id>/).
Violations
Section titled “Violations”violations: … recorded, each absorbed by its owner (the latest last)- Cause: A
violation.recordedrow indicates a runtime fault absorbed by a supervisor closure (adapter_undeliverable,pulse_stopped, orlease_unsettled).hale dna boarddisplays the 5 most recent violations.
A performer with no effect class
Section titled “A performer with no effect class”hale dna work: dna/org/work.hl: the … performer declares no effect class; every performer declares one: effect_free, idempotent or uncertain- Fix: Add
effect: "effect_free","idempotent", or"uncertain"to every performer constructed indna/org/work.hl.
Nothing to claim
Section titled “Nothing to claim”no admitted attempt awaits a … leg with capabilities `…`: … of that kind outstanding, …- Cause:
hale dna work nextfound no claimable attempt matching your--asposition,--kind,--capabilities,--classes, and performer effect class. The refusal message breaks down why each outstanding attempt was skipped.
The lease is gone
Section titled “The lease is gone”stale: the lease on attempt … expired at …stale: the lease on attempt … is …'s at token …, not …'s at token …the lease on attempt … was taken as a performer of effect class `…`; an outcome under it names that class, not `…`the lease … held at token … expired at … with no outcome; the attempt is asked again- Cause: The attempt lease expired before
submitorrenew, was claimed by another worker token, orsubmit --effectdid not match the claim’s effect class. When a lease expires without an outcome, the spine logsviolation.recorded(lease_unsettled) once and re-queues the attempt (unless the performer wasuncertain, which transitions tounresolved).
No allowance
Section titled “No allowance”the call would cost … micro-dollars, past what is left of the attempt's allowance (… of …): no call was madethe attempt's allowance of … micro-dollars is spent (…); the budget gate admits no further call- Cause: The attempt exhausted its per-attempt spend allowance or the organization’s daily budget window (The budget is spent).
Schedules
Section titled “Schedules”See Schedules.
hale dna: schedule declare: refused: schedule …: …hale dna: schedule declare: --every … is no interval (a number and ms, s, m, h or d)hale dna: schedule pause: no schedule `…` is declared in the record- Common refusal reasons:
nobody holds position:…: it convenes nothing until someone does (`hale dna fill … <holder>`): Assign and ratify a holder for the convener position first.no definition `…` in the catalog: Runhale dna definitionsto list valid workflow definitions.… takes `…`, which the inputs do not name/its inputs are a JSON object: Provide all required definition inputs as a JSON object in--args.cron `…`: a cron expression has five fields (minute hour day-of-month month day-of-week), not …: Fix the 5-field UTC cron syntax.… holds neither the Board nor position:…: declaring a schedule is the Board's, or the convener's own holder's: Run--asa user who holdsposition:boardor the convener position.
The Head and the Face
Section titled “The Head and the Face”The head will not start
Section titled “The head will not start”hale dna ui: trusted-local is a fixture's mode (HALE_DNA_TRUSTED_LOCAL=1); a project serves its head under OIDC: `git config dna.principal oidc` with dna.oidc.issuer, dna.oidc.client and dna.oidc.member (dna/face/start.sh sets them up with the stub provider, dna/oidc)hale dna ui: dna.principal is oidc, so dna.oidc.issuer, dna.oidc.client and dna.oidc.redirect must be sethale dna ui: the issuer must be https (plain http only on this machine): …hale dna ui: the issuer … is not usable: …- Fix: Start the local head via
hale dna head <project>(which configures the local stub OIDC provider automatically), or configure an HTTPS OIDC issuer (or a loopback issuer with a pinneddna.oidc.key):Terminal window git config dna.principal oidcgit config dna.oidc.issuer https://issuer.examplegit config dna.oidc.client acme-headgit config dna.oidc.redirect https://head.example/callbackgit config --add dna.oidc.member "<subject>=alice"hale dna secret set OIDC_CLIENT_SECRET
A verdict refused
Section titled “A verdict refused”review … refused the verdict: …- Refusal causes & fixes:
digest mismatch: … != …: The candidate digest changed or--digestwas stale. Re-runhale dna review <id>and approve the current digest.authority … does not satisfy …: Pass an--authoritythat meets or exceeds the Review’s required authority (boardfor Board-level Reviews).reviewer … authored the candidate: Code changes require two-person review (someone other than the candidate’s author must approve).reviewer … holds no position this Review requires (…): The Review is routed (hale dna route); only holders of the required signer positions may approve it.
hale dna: review: a verdict cites no gate run; the host observes each gate's run at the forge (hale dna forge sync): Remove--evidence; CI gate runs are observed automatically from the forge.
Every Review needs the Board
Section titled “Every Review needs the Board”- Cause: By default, tasks without an explicit class are planned as
applicationchanges, while the defaultdna::Grantindna/org/main.hlonly delegates"refactor docs"to the Leader. - Fix: Approve Reviews as the Board (
hale dna review <id> approve), or widengrant.classesindna/org/main.hlvia a reviewed change (Shaping and governing it).
A person’s task that never settles
Section titled “A person’s task that never settles”- Cause: A human-assigned Task only settles when its assignee runs
hale dna task done <id> --as <assignee>with any required evidence (--evidence <digest>) or third-party exception (--exception <why> --authorized-by <who>). Inspect open human tasks withhale dna board.
Starting Over with a Fresh Record
Section titled “Starting Over with a Fresh Record”Because the record lives in a dedicated Postgres schema (dna_<record-identity>) while applied code changes remain in git log, you can reset a project to a clean record without losing its source code:
- Remove the
record = "…"line under[dna]inhale.toml. - (If the clone still has legacy
refs/dna/Git refs, delete them—see No record, or a toolchain that moved.) - Re-graft the existing directory to provision a fresh record identity and Postgres schema:
Terminal window hale habitat graft . --attach
See Reference: Where things live for a complete map of all state locations.