Build intelligence
Matrix runs
Run IDs, live status, retries, resume, and release manifests.
Matrix Run IDs
Every matrix execution gets a Matrix Run ID (mx_20260909_8f31) - printed during the build, recorded in builds/matrix/report.json (run_id), attached to each artifact in versions.json (matrix_run_id), and persisted locally under ~/.phelix/matrix/runs/. The run snapshots the effective configuration at execution time, so later phelix.yaml edits never rewrite what an old run says it built.
List, show, and status
phelix matrix list # recorded runs, newest first (--limit N caps the list; default 20, 0 = all)phelix matrix list --json # machine-readable summariesphelix matrix show mx_20260909_8f31 # config snapshot + per-combination resultsphelix matrix show mx_20260909_8f31 --jsonphelix matrix status # live state of the active run (or "No active Matrix Run.")phelix matrix status mx_20260909_8f31 # a specific run's statephelix matrix status --json # machine-readable current statephelix matrix retry mx_20260909_8f31 --failed # retry a run's failures in a new linked runphelix matrix init # interactive wizard → writes phelix.yamlmatrix show displays the configuration snapshot (with each dimension's origin: cli, phelix.yaml, or default, plus the effective include/exclude rules and retry budget), every combination's status, duration, attempt history, artifact, SHA-256, and redacted error details for failures. An unknown run ID is a clean NOT_FOUND error. Local run history can be relocated with PHELIX_DATA_DIR like all Phelix state.
Run statuses: succeeded (everything passed), partial (some passed, some failed), failed (nothing passed), interrupted (stopped with incomplete combinations - resumable). A run with failures never reports succeeded.
matrix status answers what is happening right now - unlike matrix list (all recorded runs) and matrix show (full inspection of one run). With no argument it selects the active run: a run whose persisted status is running and whose execution lock is held by a live process. When several runs execute concurrently, the most recently started one is shown (with a note); name a run explicitly to inspect another. If nothing is executing it prints No active Matrix Run. and points at the most recent run - never a fabricated status.
Matrix Run mx_20260910_4613 - myapp (go)Status: running - executing (PID 3372540)Started: 2026-09-10 01:49:03 4 combination(s)────────────────────────────────────────────── ✓ go1.27-linux-amd64 231ms Binary: 5621907 B SHA256: 3263d01d02d8… ⟳ go1.27-darwin-arm64 7.2s ◌ go1.27-linux-arm64 pending ◌ go1.27-windows-amd64 pending──────────────────────────────────────────────Progress: 1/4 - success 1 · failed 0 · running 1 · pending 2- Live state comes from the same persisted run records the execution engine writes - after every attempt start and every completed combination. There is no second status system that could drift.
--jsonalways emits JSON:{"active": false}(plusmost_recent_runwhen any history exists) when nothing is executing - never human text on the JSON stream.- Running combinations show elapsed time (duration only - the build engine has no percentage to report) and the in-flight attempt when automatic retries are configured (
attempt 2/3). Completed combinations show their final attempt count. - Counters are always internally consistent:
success + failed + running + pending + skipped = total. - A persisted
runningrun whose executing process is gone (crash, SIGKILL, machine restart) is reported as orphaned - resumable, not executing; its in-flight combinations show asstale. A completed run never reportsrunning.
Automatic retries
phelix build myapp --matrix --matrix-retries 2--matrix-retries 2 means two additional attempts (at most 3 executions per combination), not two total. Only failed combinations are retried - a combination that succeeded is never re-executed. Retries happen inside the same Matrix Run: the run records every attempt (Attempts: 3, final: succeeded) in matrix show and report.json, so an eventual success is explainable.
Only transient failures are retried (network, connection, timeout, Docker daemon unavailable); deterministic failures - compiler errors, invalid versions, configuration problems - fail on the first attempt and are not repeated.
Resume
phelix build myapp --matrix --resume # most recent interrupted runphelix build myapp --matrix --resume=mx_20260909_8f31Resume continues an existing incomplete run instead of starting a new matrix. Runs persist incrementally (after every completed combination), so resume works after Ctrl-C, a crash, or a machine restart. Exact semantics:
succeededcombinations → never rebuiltfailedcombinations → skipped (retry them explicitly withphelix matrix retry <run-id> --failed)pendingcombinations andrunningcombinations whose worker is gone → executed
Resume uses the run's original configuration snapshot (dimensions, include/exclude rules, build args) - later phelix.yaml edits cannot change what the resumed run builds. Explicit --build-arg flags on the resume invocation are the one exception: they replace the snapshot's build args (and the run record reflects the args actually used). A per-run lock file guards against two concurrent executions of the same run; a lock left by a dead process is reclaimed automatically after a restart. The first Ctrl-C stops the run cleanly (in-flight builds are killed and stay pending); a second one terminates immediately. --matrix-dry-run alongside --resume previews the combinations a resume would execute without touching the run.
Manual retry
phelix matrix retry mx_20260909_8f31 --failedA manual retry creates a new run containing only the combinations whose final status in the source run is failed. The new run records its parent (parent_run_id), executes the source run's configuration snapshot, and the original run's history stays unchanged.
Recovery mechanisms
The three recovery mechanisms are deliberately distinct:
| Mechanism | Same run? | What executes |
|---|---|---|
| Automatic retry (--matrix-retries) | yes | failed combinations, extra attempts |
| Resume (--resume) | yes | incomplete combinations only |
| Manual retry (matrix retry --failed) | new linked run | failed combinations of the source run |
Interactive wizard: phelix matrix init walks through ecosystem, versions (recent suggestions plus free-form entry), platforms, concurrency, optional include entries (combinations outside the base matrix) and exclusions (picked from the real expanded list), then previews the actual expanded combination list with base/included/excluded counts - never a naive versions × platforms count - before writing the profile. It preserves all unrelated phelix.yaml keys and comments, offers Edit / Keep / Disable / Cancel when a profile already exists, and refuses to run without an interactive terminal (so it never hangs in CI).
Artifact checksums
Every successful matrix artifact carries a SHA-256 checksum computed from the final artifact bytes - streamed, never loaded into memory wholesale. For Docker image artifacts (matrix dockerize), the checksum is the image's content digest (docker image inspect), Docker's own SHA-256 of the image.
Checksums appear:
- in the build summary and
matrix show/matrix status(shortened, e.g.SHA256: 3263d01d02d8...), - in full, machine-readably, in
builds/matrix/report.json(sha256per combination),matrix status --json, each artifact row inversions.json(sha256), and the release manifest below.
A checksum that cannot be computed is an artifact-integrity failure: the combination is recorded as failed - an artifact whose bytes cannot be verified is never recorded as a valid release artifact (and integrity failures are never auto-retried). Retries and resume never reuse stale checksums: only the final successful attempt's artifact is checksummed and recorded; resumed runs keep the checksums of previously-succeeded combinations untouched.
Release manifest
Each finished run with successful artifacts also writes a release manifest describing that artifact set, stored next to the run record as <PHELIX_DATA_DIR>/matrix/runs/<run-id>.manifest.json:
{ "manifest_version": 1, "app": "myapp", "version": 11, "matrix_run_id": "mx_20260910_3915", "created_at": "2026-09-10T01:46:38+03:30", "status": "complete", "total_combinations": 4, "language": "go", "toolchain_versions": ["1.27"], "platforms": ["linux/amd64", "darwin/arm64", "linux/arm64", "windows/amd64"], "artifacts": [ { "combination_id": "go1.27-linux-amd64", "identity": "mx_20260910_3915/go1.27-linux-amd64", "toolchain": "go", "toolchain_version": "1.27", "platform": "linux/amd64", "os": "linux", "arch": "amd64", "artifact": "builds/matrix/go1.27-linux-amd64/myapp_amd64_go_1.27", "size_bytes": 2433430, "sha256": "dfef784b056f..." } ]}Completeness is explicit and fail-closed:
| Run outcome | Manifest |
|---|---|
| all combinations succeeded | written, status: "complete" |
| some succeeded, some failed | written, status: "partial" (only successful artifacts listed) |
| nothing succeeded / interrupted / still running | not written |
A partial matrix can therefore never masquerade as a complete release - the status field and the total_combinations count make the gap machine-checkable. Artifacts are sorted by combination ID, so the same run and artifact set always produce the same manifest. matrix show <run-id> renders the release summary (version, status, artifact count, manifest path). An interrupted run gains its manifest only once a --resume finishes it; a manual retry run gets its own manifest - runs are never merged. Docker matrix builds (phelix dockerize --matrix) record image digests per artifact in versions.json but produce no run manifest (they are not Matrix Runs).
One release, many artifacts
A matrix build is one logical build/release of one application version that produces multiple artifacts. Matrix combinations are artifacts of that release, never independent application versions: go1.27-linux-amd64, go1.27-linux-arm64, ... are artifact names (and filenames), while versions.json records exactly one version row (vN) carrying all of them:
Application └── Version vN (one versions.json row) └── Matrix Run mx_... └── Artifacts: go1.27-linux-amd64, go1.27-linux-arm64, ...