You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Machine-readable structured output for terramate list.
Background
Users need to consume stack metadata programmatically — for CI/CD workflow matrices, external orchestration config generation (e.g. Atlantis), and automation scripts. The current text output requires fragile shell parsing and cannot represent structured data like dependencies, bundle provenance, or sharing relationships.
CLI Interface
terramate list --format json [--include-host-dir] [--include-change-status] [--include-cloud-status] [existing flags...]
--format json — outputs JSON array to stdout. Extensible to other formats later.
--include-host-dir — opt-in: adds host object (filesystem paths) to all path objects.
--include-change-status — opt-in: runs change detection and adds status.changed and status.change_reasons to every stack. Does not filter stacks — all stacks are still returned. Implicitly enabled when --changed is used.
--include-cloud-status — opt-in: queries Terramate Cloud and adds status.cloud to every stack.
Compatible with all existing list flags: --changed, --tags, -C, --run-order, --target, --why, cloud filters, etc. --why is redundant with --format json since change_reasons provides the same information — the flag is accepted but has no additional effect on JSON output.
JSON on stdout, human messages on stderr.
Existing terramate list behavior is unchanged when --format is not specified.
Status block behavior
The status object is absent when no status flags are active. Its fields appear based on flags:
relative means relative to the owning/referencing stack's directory. For dependency refs this is the stack that declares the before/after/wants/wanted_by. For watch paths this is the stack that declares the watch. This matches how these references are written in HCL.
Project-absolute path (from repo root, starts with /)
Always
relative
Relative to the owning/referencing stack's directory
Nested refs only (deps, parent, sharing, watch)
host.abs
Filesystem absolute path
--include-host-dir only
host.rel
Filesystem relative to CWD or -C chdir
--include-host-dir only
Field Reference
Stack fields (always present)
Field
Type
Description
id
string|null
Stack UUID. null when no ID is set in the stack definition.
name
string
Stack name (merged result)
description
string
Stack description (merged result)
path
object
Path object (see above)
tags
string[]
Tags, sorted alphanumerically (merged result)
watch
object[]
Watch files with resolved paths and existence flag (merged result)
is_nested
bool
true if stack has a parent stack
parent
object|null
Parent stack reference (id + path), or null
raw
object
Stack's own values from stack {} block before bundle merging
before
object
Before dependencies (merged result, see dependency object)
after
object
After dependencies (merged result, see dependency object)
wants
object
Wants dependencies (merged result, see dependency object)
wanted_by
object
Wanted-by dependencies (merged result, see dependency object)
sharing
object
Output sharing info (see sharing object). Absent when stack has no sharing config.
status
object
Status information. Absent when no --include-*-status or --changed flags are used. See status object.
bundles
array
Bundles managing this stack. Absent when stack has no bundles.
components
array
Active components in this stack. Absent when stack has no active components.
Top-level scalar fields (name, description) and list fields (tags, watch) show the merged result after all bundle contributions. The raw object and bundles[].metadata allow tracing each value to its origin.
Naming note: At the stack level, pre-merge values are called raw because they represent the literal HCL config from the stack {} block. At the bundle and component level, the equivalent is called definition because it maps to the define bundle / define component blocks in the source.
Raw object
The stack's own values from the stack {} block, exactly as the user configured them (before normalization or bundle merging):
refs — per-reference detail: raw ref string, type ("tag" for tag: filter expressions, "path" for relative/absolute paths), and resolved stacks (one ref can resolve to multiple stacks via tags or directory matching). When a ref resolves to nothing (e.g. deleted stack, no matching tags), stacks is an empty array.
stacks — flat deduplicated list of all resolved stacks across all refs
Sharing object
Absent when the stack has no input or output blocks configured.
No value expressions — only structural references.
from_stack uses the same stack reference pattern (id + path).
Status object
Absent when no status flags are active. Contains fields based on flags.
Change detection fields (with --include-change-status or --changed)
Changed stack:
{
"changed": true,
"change_reasons": [
{ "message": "stack has been triggered by: /stacks/networking/vpc/.tmtrigger" }
]
}
Unchanged stack (only with --include-change-status, never with --changed alone since those are filtered out):
{
"changed": false
}
changed — bool, always present when change detection is active
change_reasons — absent when changed is false. Must be an array of objects even if the current implementation only returns one reason per stack — this is a forward-compatibility requirement to support multiple reasons and additional fields (e.g. type, file, details) in the future without a breaking schema change.
SPEC:
terramate list --format jsonMachine-readable structured output for
terramate list.Background
Users need to consume stack metadata programmatically — for CI/CD workflow matrices, external orchestration config generation (e.g. Atlantis), and automation scripts. The current text output requires fragile shell parsing and cannot represent structured data like dependencies, bundle provenance, or sharing relationships.
CLI Interface
--format json— outputs JSON array to stdout. Extensible to other formats later.--include-host-dir— opt-in: addshostobject (filesystem paths) to allpathobjects.--include-change-status— opt-in: runs change detection and addsstatus.changedandstatus.change_reasonsto every stack. Does not filter stacks — all stacks are still returned. Implicitly enabled when--changedis used.--include-cloud-status— opt-in: queries Terramate Cloud and addsstatus.cloudto every stack.--changed,--tags,-C,--run-order,--target,--why, cloud filters, etc.--whyis redundant with--format jsonsincechange_reasonsprovides the same information — the flag is accepted but has no additional effect on JSON output.terramate listbehavior is unchanged when--formatis not specified.Status block behavior
The
statusobject is absent when no status flags are active. Its fields appear based on flags:status.changedstatus.change_reasonsstatus.cloud--format json--format json --changedtrue(always)--format json --include-change-statustrueorfalsetrue, absent whenfalse--format json --include-cloud-status--format json --changed --include-cloud-statustrue(always)Note:
--changedimplicitly enables--include-change-status.Top-Level Structure
JSON array of stack objects:
[ { ... stack object ... }, { ... stack object ... } ]When no stacks match the filters, the output is an empty array
[].Stack Object
{ "id": "a1111111-1111-1111-1111-111111111111", "name": "vpc", "description": "VPC networking stack", "path": { "absolute": "/stacks/networking/vpc" }, "tags": ["aws", "core", "network"], "watch": [ { "path": { "absolute": "/config/networking.cfg", "relative": "../../../config/networking.cfg" }, "exists": true } ], "is_nested": true, "parent": { "id": "p1111111-1111-1111-1111-111111111111", "path": { "absolute": "/stacks/networking", "relative": ".." } }, "raw": { "id": "a1111111-1111-1111-1111-111111111111", "name": "vpc", "description": "VPC networking stack", "tags": ["core"], "before": [], "after": [], "wants": [], "wanted_by": [], "watch": ["../../../config/networking.cfg"] }, "before": { "refs": [], "stacks": [] }, "after": { "refs": [ { "ref": "tag:hub", "type": "tag", "stacks": [ { "id": "h1111111-1111-1111-1111-111111111111", "path": { "absolute": "/stacks/networking/hub", "relative": "../hub" } } ] }, { "ref": "../dns", "type": "path", "stacks": [ { "id": "d1111111-1111-1111-1111-111111111111", "path": { "absolute": "/stacks/networking/dns", "relative": "../dns" } } ] } ], "stacks": [ { "id": "h1111111-1111-1111-1111-111111111111", "path": { "absolute": "/stacks/networking/hub", "relative": "../hub" } }, { "id": "d1111111-1111-1111-1111-111111111111", "path": { "absolute": "/stacks/networking/dns", "relative": "../dns" } } ] }, "wants": { "refs": [], "stacks": [] }, "wanted_by": { "refs": [], "stacks": [] }, "sharing": { "outputs": [ { "name": "vpc_id", "backend": "terraform" } ], "inputs": [ { "name": "hub_vpc_id", "backend": "terraform", "from_stack": { "id": "h1111111-1111-1111-1111-111111111111", "path": { "absolute": "/stacks/networking/hub", "relative": "../hub" } } } ] }, "status": { "changed": true, "change_reasons": [ { "message": "stack has been triggered by: /stacks/networking/vpc/.tmtrigger" } ], "cloud": { "stack_status": "ok", "deployment_status": "ok", "drift_status": "ok" } }, "bundles": [ { "instance": { "name": "my-vpc", "uuid": "b1111111-1111-1111-1111-111111111111", "alias": "vpc-main", "source": "example.com/tf-aws-vpc/v1", "environment": "production" }, "definition": { "class": "infrastructure", "name": "AWS VPC", "version": "1.0.0", "description": "Provisions a VPC with subnets" }, "metadata": { "name": "vpc", "description": "VPC networking stack", "tags": ["aws", "network"], "before": [], "after": ["tag:hub", "../dns"], "wants": [], "wanted_by": [], "watch": ["../../../config/networking.cfg"] } } ], "components": [ { "instance": { "name": "vpc", "source": "./components/vpc", "resolved_source": "/components/example.com/tf-aws-vpc/v1" }, "definition": { "class": "terraform", "name": "AWS VPC Component", "version": "1.0.0", "description": "Terraform VPC module" }, "bundle": { "class": "infrastructure", "alias": "vpc-main", "uuid": "b1111111-1111-1111-1111-111111111111" } }, { "instance": { "name": "monitoring", "source": "./components/monitoring", "resolved_source": "/components/monitoring/v1" }, "definition": { "class": "observability", "name": "Monitoring", "version": "2.0.0", "description": "Monitoring setup" }, "bundle": null } ] }Path Object
The
pathobject appears in multiple places with context-dependent fields.Top-level stack path (default)
Top-level stack path (with
--include-host-dir)Nested reference path (dependency, parent, sharing, watch)
relativemeans relative to the owning/referencing stack's directory. For dependency refs this is the stack that declares thebefore/after/wants/wanted_by. For watch paths this is the stack that declares thewatch. This matches how these references are written in HCL.Nested reference path (with
--include-host-dir)Field definitions
absolute/)relativehost.abs--include-host-dironlyhost.rel-Cchdir--include-host-dironlyField Reference
Stack fields (always present)
idnullwhen no ID is set in the stack definition.namedescriptionpathtagswatchis_nestedtrueif stack has a parent stackparentid+path), ornullrawstack {}block before bundle mergingbeforeafterwantswanted_bysharingstatus--include-*-statusor--changedflags are used. See status object.bundlescomponentsTop-level scalar fields (
name,description) and list fields (tags,watch) show the merged result after all bundle contributions. Therawobject andbundles[].metadataallow tracing each value to its origin.Raw object
The stack's own values from the
stack {}block, exactly as the user configured them (before normalization or bundle merging):{ "id": "a1111111-1111-1111-1111-111111111111", "name": "vpc", "description": "VPC networking stack", "tags": ["core"], "before": [], "after": [], "wants": [], "wanted_by": [], "watch": ["../../../config/networking.cfg"] }All values are the literal HCL config values. Watch paths are the raw strings as written (relative or absolute), not resolved.
id— present only when explicitly set in thestack {}block, omitted otherwiseMerge rules:
name,description: stack definition wins; bundle value used only if stack's own value is emptytags,before,after,wants,wanted_by,watch: union of stack + all bundle contributions, deduplicated, sorted alphanumericallyDependency object (
before,after,wants,wanted_by){ "refs": [ { "ref": "<raw reference as written in stack definition>", "type": "tag|path", "stacks": [ { "id": "...", "path": { "absolute": "...", "relative": "..." } } ] } ], "stacks": [ { "id": "...", "path": { "absolute": "...", "relative": "..." } } ] }refs— per-reference detail: rawrefstring,type("tag"fortag:filter expressions,"path"for relative/absolute paths), and resolved stacks (one ref can resolve to multiple stacks via tags or directory matching). When a ref resolves to nothing (e.g. deleted stack, no matching tags),stacksis an empty array.stacks— flat deduplicated list of all resolved stacks across all refsSharing object
Absent when the stack has no
inputoroutputblocks configured.{ "outputs": [ { "name": "...", "backend": "..." } ], "inputs": [ { "name": "...", "backend": "...", "from_stack": { "id": "...", "path": { "absolute": "...", "relative": "..." } } } ] }valueexpressions — only structural references.from_stackuses the same stack reference pattern (id + path).Status object
Absent when no status flags are active. Contains fields based on flags.
Change detection fields (with
--include-change-statusor--changed)Changed stack:
{ "changed": true, "change_reasons": [ { "message": "stack has been triggered by: /stacks/networking/vpc/.tmtrigger" } ] }Unchanged stack (only with
--include-change-status, never with--changedalone since those are filtered out):{ "changed": false }changed— bool, always present when change detection is activechange_reasons— absent whenchangedisfalse. Must be an array of objects even if the current implementation only returns one reason per stack — this is a forward-compatibility requirement to support multiple reasons and additional fields (e.g.type,file,details) in the future without a breaking schema change.status.cloud(with--include-cloud-status)Stack synced to Cloud:
{ "stack_status": "ok", "deployment_status": "ok", "drift_status": "ok" }Stack is a draft:
{ "stack_status": "ok", "deployment_status": "ok", "drift_status": "ok", "draft": true }Stack not in Cloud:
{ "missing": true }missing— absent whenfalse, only present whentruedraft— absent whenfalse, only present whentruestack_status,deployment_status,drift_status— absent whenmissingistrueStatus values per field:
stack_statusok,drifted,failed,unrecognizeddeployment_statusok,pending,running,failed,canceled,unrecognizeddrift_statusok,unknown,drifted,failed,running,unrecognizedBundle object
{ "instance": { "name": "...", "uuid": "...", "alias": "...", "source": "...", "environment": "..." }, "definition": { "class": "...", "name": "...", "version": "...", "description": "..." }, "metadata": { "name": "...", "description": "...", "tags": [], "before": [], "after": [], "wants": [], "wanted_by": [], "watch": [] } }instance— the bundle instance (frombundle "name" {}block)definition— the bundle definition metadata (fromdefine bundle { metadata {} })metadata— the fields/values the bundle contributes to this stack (duplicates of top-level fields showing bundle's contribution)Component object
{ "instance": { "name": "...", "source": "...", "resolved_source": "..." }, "definition": { "class": "...", "name": "...", "version": "...", "description": "..." }, "bundle": { "class": "...", "alias": "...", "uuid": "..." } }instance— the component instancedefinition— the component definition metadata (fromdefine component { metadata {} })bundle— reference to originating bundle (nullif standalone component)Scope
In scope
terramate list --format jsonwith JSON array output ([FEATURE] Json stack list output #723, [FEATURE] terramate list --run-order to include specific ordering #2069, [FEATURE] Command to get stack ID #1960, PR Add JSON format to list command #2073, PR feat: list --format {text,json,dot} --label stack.{id,name,dir} #2219)before,after,wants,wanted_by) with raw refs and resolved stacksinputs,outputs)raw+bundles[].metadata--include-change-status--include-cloud-statusOut of scope
--labelconcept (PR feat: list --format {text,json,dot} --label stack.{id,name,dir} #2219 — not needed)run --idfiltering ([FEATURE] Add the ability to run against a stack by name or id #1353 — separate feature)Related Issues & PRs
list --run-orderto include specific orderingrun --idremains open)list --format {text,json,dot} --label