Skip to content

JSON output

Every command that emits JSON carries a schema field naming the payload and its major version:

{
"schema": "trellis.list/1",
"packages": [
{
"name": "lat_core",
"version": "1.2.0",
"path": "packages/lat_core",
"lifecycle": "hex",
"releasable": true,
"dependencies": [],
"dependents": ["lat_mid", "lat_cli"]
}
]
}

An unrecognized schema means an unsupported version — assert on it.

Change Allowed?
Adding a field Yes, without a version bump — ignore fields you don’t know.
Adding an array element Yes, per the command’s documented selection rules.
Renaming, removing, or retyping a field; changing a documented enum’s values (kind, action, tag_kind) No — bumps the major (trellis.list/1/2).

Not guaranteed: key order (parse as JSON) or whitespace (some commands pretty-print, some emit one line for shell-variable capture).

Human-readable output isn’t covered. It’s written for a terminal, is TTY-dependent, and changes freely. Use --json if you’re parsing.

Command Schema and top-level keys
list --json trellis.list/1
{schema, packages[]}
info <pkg> --json trellis.info/1
{schema, name, version, path, lifecycle, releasable, dependencies[], dependents[]}
graph --format json trellis.graph/1
{schema, nodes[], edges[]}
run <task> --json trellis.run/1
{schema, ok, task, target?, results[]}
exec -- <cmd> --json trellis.exec/1
{schema, ok, command[], results[]}
changelog check --format json trellis.changelog_check/2
{schema, ok, strictness, has_entries, needs_entry, invalid_fragments[], packages[], preview}
version plan --json trellis.version_plan/1
{schema, bumped[], fragments_retained}
version apply --json trellis.version_apply/1
{schema, bumped[], lockfiles[], adopted[], fragments_retained}
tag plan --json trellis.tag_plan/2
{schema, tags[]}
ci tag-package --json trellis.ci_tag_package/1
{schema, name, path, version, tag_kind, tag_version | tag_series}
doctor --format json trellis.doctor/1
{schema, ok, packages, configless, auto_members, findings[], fixes[], applied[], package_lifecycles[]}

Notes:

  • list’s packages[] entries and info’s top level share a shape. lifecycle is the resolved release lifecycle (workspace, git_only, or hex); releasable is a derived boolean (lifecycle != workspace), kept for compatibility with the git_only+hex meaning --releasable has always had. graph nodes also carry both.
  • version plan/version apply share the bumped[] shape (name, current, next, fragments, updated_dependencies[]). fragments_retained is true only under --pre <label>.
  • run/exec share the results[] shape — package, path, status (success | failed | skipped), duration_ms. A failed entry also carries exit_code and command. duration_ms’s presence is stable, its value isn’t.
  • ci tag-package reports tag_kind: exact with tag_version, or tag_kind: series with tag_series — the other key is absent, not null.
  • tag plan entries use kind: repository_series for an anchored repository tag. The entry retains the anchor package’s name and version; this kind is never returned by package-oriented ci tag-package.
  • changelog check’s preview field is always present and a string, but its Markdown prose can change without a version bump. needs_entry reports whether an entry is missing; ok reports what changelog.strictness made of that, and matches the exit code. --json is a deprecated alias for --format json.

--json moves package output to stderr. run/exec normally stream package output to stdout; under --json it moves to stderr so stdout carries only the payload. -q suppresses the stream.

package_lifecycles is an array of {name, lifecycle}, one per member in workspace order, alongside the numeric packages count. doctor’s text summary renders the same data as compact counts: ok: 4 package(s) (1 workspace, 0 git_only, 3 hex), 0 warning(s).

findings[] carries one entry per problem:

{
"check": "lockfile_drift",
"severity": "error",
"message": "packages/lat_mid/manifest.toml locks `lat_core` at 1.1.0 …",
"file": "packages/lat_mid/manifest.toml",
"package": "lat_mid",
"fixable": true
}

severity is error or warning (only error fails the run). file/ package are absent, not null, when a finding isn’t tied to one file. message is prose, not stable — branch on check:

check Raised when
member_glob a members glob is invalid, unreadable, or matches nothing
package_manifest a package’s gleam.toml is missing, unparseable, carries a [tools.trellis] table, or repeats another package’s name
path_dependency a path dependency escapes the workspace or names no package in it
dependency_cycle the graph is cyclic
workspace_config the root [tools.trellis] table is itself wrong
exclusion_glob a task-exclusion, tag-mode-override, or publish.lifecycle.packages glob matches no member
release_boundary a package’s runtime path dependency is less capable than it is (a hex package depending on git_only/workspace, or git_only on workspace)
tag_collision two releasable packages produce the same tag
lockfile_drift a manifest.toml locks a workspace-internal dep at a stale version
changelog_missing a releasable package has no CHANGELOG.md
changelog_unreadable a CHANGELOG.md exists but could not be read
changelog_behind a package’s version is behind the newest one in its changelog
changelog_adoption a package has pre-trellis changelog history the next release will adopt
package_version a package’s version is not valid semver
changelog_fragment an unreleased fragment does not parse, or names an unknown package or kind
toolchain the gleam on PATH disagrees with the .tool-versions pin
shared_dependency packages require different versions of the same external dependency

New check values may appear without a bump — handle unrecognized ones by reporting them, not dropping them.

--format github is not JSON. It emits workflow commands (::error title=lockfile_drift,file=packages/lat_mid/manifest.toml::…) so findings land on a PR’s Files tab. See CI recipes.

ci matrix and ci outputs carry no schema — their shapes are dictated by GitHub Actions, not trellis.

ci matrix emits one key, fed straight to strategy.matrix via fromJSON():

{"include":[{"name":"lat_core","path":"packages/lat_core","version":"1.2.0"}]}

A schema sibling here would be a bug: every top-level key besides include becomes another matrix axis.

ci outputs emits key=value lines for $GITHUB_OUTPUT, each value a JSON array of strings:

packages=["lat_core","lat_mid","lat_cli","lat_example"]
projects=["lat_core","lat_mid","lat_cli","lat_example"]
releasable=["lat_core","lat_mid","lat_cli"]
version_files=["packages/lat_core/gleam.toml","packages/lat_mid/gleam.toml","packages/lat_cli/gleam.toml"]
tags=["lat_core-v1.2.0","lat_mid-v0.5.1","lat_cli-v0.4.3"]
series_tags=[]

projects duplicates packages and is deprecated; it goes away at 1.0.

changelog check --format github emits the same style of lines — scalars plain, arrays as JSON, and the multi-line preview in GitHub’s heredoc form. It is the trellis.changelog_check/2 data in a shape the runner parses, not a payload of its own, so it carries no schema either.

See CI recipes for how these are consumed.

Every payload is snapshotted in trellis’s test suite, so a change to a stable shape fails CI before it can ship.