Configuration fields
This reference describes v4.2.4. Use Environment and secrets and Lifecycle for workflows. Fields below come from the version-pinned schema and readers, not a newer checkout.
Project JSON
The legacy project file is .hack/hack.config.json. Requiredness below is relative to the containing schema object or union variant. An optional parent does not become required because a child is required. Repeated paths describe different variants. The schema requires name and dev_host; the reader has compatibility fallbacks. A schema constraint is not proof the reader validates it. No schema default is declared for these fields.
| Field | Type | Within this variant | Constraints | Schema location | Evidence |
|---|---|---|---|---|---|
$schema | string | Optional | — | #/properties/$schema | source |
name | string | Required | — | #/properties/name | source |
dev_host | string | Required | — | #/properties/dev_host | source |
defaultEnvConfig | string | Optional | — | #/properties/defaultEnvConfig | source |
default_env_config | string | Optional | — | #/properties/default_env_config | source |
logs | object | Optional | {"additionalProperties":true} | #/properties/logs | source |
logs.follow_backend | string | Optional | {"enum":["compose","loki"]} | #/properties/logs/properties/follow_backend | source |
logs.snapshot_backend | string | Optional | {"enum":["compose","loki"]} | #/properties/logs/properties/snapshot_backend | source |
logs.clear_on_down | boolean | Optional | — | #/properties/logs/properties/clear_on_down | source |
logs.retention_period | string | Optional | — | #/properties/logs/properties/retention_period | source |
internal | object | Optional | {"additionalProperties":true} | #/properties/internal | source |
internal.dns | boolean | Optional | — | #/properties/internal/properties/dns | source |
internal.tls | boolean | Optional | — | #/properties/internal/properties/tls | source |
internal.extra_hosts | object | Optional | {"additionalProperties":{"type":"string"}} | #/properties/internal/properties/extra_hosts | source |
oauth | object | Optional | {"additionalProperties":true} | #/properties/oauth | source |
oauth.enabled | boolean | Optional | — | #/properties/oauth/properties/enabled | source |
oauth.tld | string | Optional | — | #/properties/oauth/properties/tld | source |
open | object | Optional | {"additionalProperties":true} | #/properties/open | source |
open.prefer | string | Optional | {"enum":["auto","alias","dev"]} | #/properties/open/properties/prefer | source |
worktree | object | Optional | {"additionalProperties":true} | #/properties/worktree | source |
worktree.auto_branch | boolean | Optional | — | #/properties/worktree/properties/auto_branch | source |
sessions | object | Optional | {"additionalProperties":true} | #/properties/sessions | source |
sessions.mux | string | Optional | {"enum":["auto","tmux","zellij","none"]} | #/properties/sessions/properties/mux | source |
startup | array | Optional | — | #/properties/startup | source |
startup[].name | string | Optional | — | #/properties/startup/items/anyOf/1/properties/name | source |
startup[].run | string | Optional | — | #/properties/startup/items/anyOf/1/properties/run | source |
startup[].command | string | Optional | — | #/properties/startup/items/anyOf/1/properties/command | source |
startup[].cwd | string | Optional | — | #/properties/startup/items/anyOf/1/properties/cwd | source |
startup[].persistent | boolean | Optional | — | #/properties/startup/items/anyOf/1/properties/persistent | source |
startup[].singleton | object | Optional | {"additionalProperties":false} | #/properties/startup/items/anyOf/1/properties/singleton | source |
startup[].singleton.ports | array | Required | {"minItems":1,"uniqueItems":true} | #/properties/startup/items/anyOf/1/properties/singleton/properties/ports | source |
startup[].singleton.onConflict | string | Optional | {"enum":["adopt","fail"]} | #/properties/startup/items/anyOf/1/properties/singleton/properties/onConflict | source |
lifecycle | object | Optional | {"additionalProperties":false} | #/properties/lifecycle | source |
lifecycle.up | object | Optional | {"additionalProperties":false} | #/properties/lifecycle/properties/up | source |
lifecycle.up.before | array | Optional | — | #/properties/lifecycle/properties/up/properties/before | source |
lifecycle.up.before[].name | string | Optional | — | #/properties/lifecycle/properties/up/properties/before/items/anyOf/1/properties/name | source |
lifecycle.up.before[].command | string | Required | — | #/properties/lifecycle/properties/up/properties/before/items/anyOf/1/properties/command | source |
lifecycle.up.before[].cwd | string | Optional | — | #/properties/lifecycle/properties/up/properties/before/items/anyOf/1/properties/cwd | source |
lifecycle.up.before[].persistent | boolean | Optional | — | #/properties/lifecycle/properties/up/properties/before/items/anyOf/1/properties/persistent | source |
lifecycle.up.before[].singleton | object | Optional | {"additionalProperties":false} | #/properties/lifecycle/properties/up/properties/before/items/anyOf/1/properties/singleton | source |
lifecycle.up.before[].singleton.ports | array | Required | {"minItems":1,"uniqueItems":true} | #/properties/lifecycle/properties/up/properties/before/items/anyOf/1/properties/singleton/properties/ports | source |
lifecycle.up.before[].singleton.onConflict | string | Optional | {"enum":["adopt","fail"]} | #/properties/lifecycle/properties/up/properties/before/items/anyOf/1/properties/singleton/properties/onConflict | source |
lifecycle.up.after | array | Optional | — | #/properties/lifecycle/properties/up/properties/after | source |
lifecycle.up.after[].name | string | Optional | — | #/properties/lifecycle/properties/up/properties/after/items/anyOf/1/properties/name | source |
lifecycle.up.after[].command | string | Required | — | #/properties/lifecycle/properties/up/properties/after/items/anyOf/1/properties/command | source |
lifecycle.up.after[].cwd | string | Optional | — | #/properties/lifecycle/properties/up/properties/after/items/anyOf/1/properties/cwd | source |
lifecycle.up.after[].persistent | boolean | Optional | — | #/properties/lifecycle/properties/up/properties/after/items/anyOf/1/properties/persistent | source |
lifecycle.up.after[].singleton | object | Optional | {"additionalProperties":false} | #/properties/lifecycle/properties/up/properties/after/items/anyOf/1/properties/singleton | source |
lifecycle.up.after[].singleton.ports | array | Required | {"minItems":1,"uniqueItems":true} | #/properties/lifecycle/properties/up/properties/after/items/anyOf/1/properties/singleton/properties/ports | source |
lifecycle.up.after[].singleton.onConflict | string | Optional | {"enum":["adopt","fail"]} | #/properties/lifecycle/properties/up/properties/after/items/anyOf/1/properties/singleton/properties/onConflict | source |
lifecycle.down | object | Optional | {"additionalProperties":false} | #/properties/lifecycle/properties/down | source |
lifecycle.down.before | array | Optional | — | #/properties/lifecycle/properties/down/properties/before | source |
lifecycle.down.before[].name | string | Optional | — | #/properties/lifecycle/properties/down/properties/before/items/anyOf/1/properties/name | source |
lifecycle.down.before[].command | string | Required | — | #/properties/lifecycle/properties/down/properties/before/items/anyOf/1/properties/command | source |
lifecycle.down.before[].cwd | string | Optional | — | #/properties/lifecycle/properties/down/properties/before/items/anyOf/1/properties/cwd | source |
lifecycle.down.before[].persistent | boolean | Optional | — | #/properties/lifecycle/properties/down/properties/before/items/anyOf/1/properties/persistent | source |
lifecycle.down.before[].singleton | object | Optional | {"additionalProperties":false} | #/properties/lifecycle/properties/down/properties/before/items/anyOf/1/properties/singleton | source |
lifecycle.down.before[].singleton.ports | array | Required | {"minItems":1,"uniqueItems":true} | #/properties/lifecycle/properties/down/properties/before/items/anyOf/1/properties/singleton/properties/ports | source |
lifecycle.down.before[].singleton.onConflict | string | Optional | {"enum":["adopt","fail"]} | #/properties/lifecycle/properties/down/properties/before/items/anyOf/1/properties/singleton/properties/onConflict | source |
lifecycle.down.after | array | Optional | — | #/properties/lifecycle/properties/down/properties/after | source |
lifecycle.down.after[].name | string | Optional | — | #/properties/lifecycle/properties/down/properties/after/items/anyOf/1/properties/name | source |
lifecycle.down.after[].command | string | Required | — | #/properties/lifecycle/properties/down/properties/after/items/anyOf/1/properties/command | source |
lifecycle.down.after[].cwd | string | Optional | — | #/properties/lifecycle/properties/down/properties/after/items/anyOf/1/properties/cwd | source |
lifecycle.down.after[].persistent | boolean | Optional | — | #/properties/lifecycle/properties/down/properties/after/items/anyOf/1/properties/persistent | source |
lifecycle.down.after[].singleton | object | Optional | {"additionalProperties":false} | #/properties/lifecycle/properties/down/properties/after/items/anyOf/1/properties/singleton | source |
lifecycle.down.after[].singleton.ports | array | Required | {"minItems":1,"uniqueItems":true} | #/properties/lifecycle/properties/down/properties/after/items/anyOf/1/properties/singleton/properties/ports | source |
lifecycle.down.after[].singleton.onConflict | string | Optional | {"enum":["adopt","fail"]} | #/properties/lifecycle/properties/down/properties/after/items/anyOf/1/properties/singleton/properties/onConflict | source |
lifecycle.processes | array | Optional | — | #/properties/lifecycle/properties/processes | source |
lifecycle.processes[].name | string | Required | — | #/properties/lifecycle/properties/processes/items/properties/name | source |
lifecycle.processes[].command | string | Required | — | #/properties/lifecycle/properties/processes/items/properties/command | source |
lifecycle.processes[].cwd | string | Optional | — | #/properties/lifecycle/properties/processes/items/properties/cwd | source |
lifecycle.processes[].singleton | object | Optional | {"additionalProperties":false} | #/properties/lifecycle/properties/processes/items/properties/singleton | source |
lifecycle.processes[].singleton.ports | array | Required | {"minItems":1,"uniqueItems":true} | #/properties/lifecycle/properties/processes/items/properties/singleton/properties/ports | source |
lifecycle.processes[].singleton.onConflict | string | Optional | {"enum":["adopt","fail"]} | #/properties/lifecycle/properties/processes/items/properties/singleton/properties/onConflict | source |
controlPlane | object | Optional | {"additionalProperties":true} | #/properties/controlPlane | source |
controlPlane.tui | object | Optional | {"additionalProperties":true} | #/properties/controlPlane/properties/tui | source |
controlPlane.tui.logs | object | Optional | {"additionalProperties":true} | #/properties/controlPlane/properties/tui/properties/logs | source |
controlPlane.tui.logs.maxEntries | number | Optional | — | #/properties/controlPlane/properties/tui/properties/logs/properties/maxEntries | source |
controlPlane.tui.logs.maxLines | number | Optional | — | #/properties/controlPlane/properties/tui/properties/logs/properties/maxLines | source |
controlPlane.tui.logs.historyTailStep | number | Optional | — | #/properties/controlPlane/properties/tui/properties/logs/properties/historyTailStep | source |
controlPlane.usage | object | Optional | {"additionalProperties":true} | #/properties/controlPlane/properties/usage | source |
controlPlane.usage.watchIntervalMs | number | Optional | — | #/properties/controlPlane/properties/usage/properties/watchIntervalMs | source |
controlPlane.usage.historySize | number | Optional | — | #/properties/controlPlane/properties/usage/properties/historySize | source |
controlPlane.daemon | object | Optional | {"additionalProperties":true} | #/properties/controlPlane/properties/daemon | source |
controlPlane.daemon.autoStart | boolean | Optional | — | #/properties/controlPlane/properties/daemon/properties/autoStart | source |
Aliases and selection
Prefer env.defaultOverlay for the default overlay. The reader checks env.defaultOverlay before env.default_overlay; nested env selection precedes legacy defaultEnvConfig, which in turn precedes default_env_config. These nested fields are read even though the legacy generated schema does not enumerate the env object. worktree.autoBranch is an alias of worktree.auto_branch; a non-null snake_case value is selected before the camelCase alias, then parsed as a boolean. Use one spelling rather than conflicting aliases.
Effective fallbacks
| Field | Fallback | Scope |
|---|---|---|
logs.follow_backend | "compose" | consumer fallback, not schema default |
logs.snapshot_backend | "loki" | consumer fallback, can fall back or be explicitly forced |
startup[].persistent | false | reader/workflow default documented in lifecycle |
worktree.auto_branch | true | reader/caller default; linked checkouts only |
values.global | {} | parser normalization |
environment | null | selected layer environment fallback, not a fixed literal |
branchRegistry.branches | [] | missing-file writer default, not proof invalid files are rewritten |
Log preferences are not forced backend selection. Follow mode prefers Compose; snapshots prefer Loki, with consumer-specific fallback. startup[].persistent defaults false. Linked-worktree auto-branch selection defaults on in the legacy Compose path; an ordinary checkout remains the base instance. Follow the runtime-specific command guide.
Hooks and processes
startup accepts string shorthand or objects. Startup objects require at least one of run or command. The reader prefers run and drops entries whose selected command is empty. They accept optional name/cwd, and persistent (false by default). lifecycle.up.before/after and lifecycle.down.before/after are hook arrays; their command-object variants require command. Do not transfer the startup-only run spelling to every hook. lifecycle.processes names long-lived processes and requires name and command. singleton.ports contains unique positive ports; singleton.onConflict selects adopt or fail. Read Lifecycle for working-directory origins, ordering, ownership and failure behavior.
Scope exclusions
Retained gateway, cluster, supervisor, shared/broker ownership and extension-manager fields are not a supported hosted product API. Legacy secret-backend strategy fields are separate from the managed YAML format below. Neither permissive unknown keys nor a retained schema field makes a backend qualified. Internal daemon, guest and compiler transport types are not public API specifications.
Managed environment YAML
Files such as .hack/hack.env.default.yaml use values, not the older JSON vars format. Use hack env to manage values and Share environment and secrets for key handling. Examples must use placeholders, never real secret values.
| Field | Type / requirement |
|---|---|
version | literal 1; required |
environment | string; optional; reader falls back to selected file/layer environment |
secretsprovider | literal project_key; required |
values | scope-to-entry map; required object |
values.{scope} | key-to-value map; each scope must be an object; omitted global normalized to {} |
values.{scope}.{KEY} | string | number | boolean | secure object; null rejected by parser; no tombstone support in this pinned format |
values.{scope}.{KEY}.secure | nonempty string; required for object-valued stored entry; ciphertext format is interpreted elsewhere |
version: 1, secretsprovider: project_key, and an object values are required. Each scope is an object. Scope names match ^[a-z0-9][a-z0-9._-]*$; keys match ^[A-Z_][A-Z0-9_]*$. Missing values.global becomes an empty object. The environment falls back to the selected layer; an example containing environment: default does not establish a universal default. Secure values require a nonempty secure string; valid shape alone does not prove decryption or key availability.
This version rejects YAML null values. Do not copy null-removal examples from v5.
The parser selects recognized fields; this is not a closed JSON-schema promise. The legacy hack.env.json schema describes a different file format.
Branch registry
.hack/hack.branches.json records CLI-managed branch metadata, not live container ownership. Use branch commands rather than hand-editing runtime directories.
| Field | Type | Schema requirement | Constraints |
|---|---|---|---|
$schema | string | Optional | {} |
version | integer | Required | {"const":1} |
branches | array | Required | {} |
branches[].name | string | Required | {"minLength":1} |
branches[].slug | string | Required | {"pattern":"^[a-z0-9][a-z0-9-]*$","minLength":1} |
branches[].note | string | Optional | {} |
branches[].created_at | string | Optional | {"format":"date-time"} |
branches[].last_used_at | string | Optional | {"format":"date-time"} |
name is the human/Git branch name; slug is its runtime-safe identifier. note is an annotation. Usage updates fill a missing created_at and refresh last_used_at; neither timestamp proves a running instance or permits deleting data. A missing registry starts empty. Invalid JSON is reported. The parser drops invalid entries and narrows recognized fields, so it does not enforce every schema pattern/date format/unknown-key constraint.
Source: v4.2.4 · docs/site/reference/configuration-fields.md.