Repository navigation
feat(ci): AgentCI owns the DAG — job outputs, run-time needs.*, one job per act (2.130.0) - #181
Merged
Merged
Conversation
…ob per act (2.130.0)
A workflow with a `changes` gate, shards on
`if: needs.changes.outputs.code == 'true'` and an aggregator reading
`${{ needs.<job>.result }}` could not run under c3_ci: every job UNSUP
with "unresolved expression(s): github.event.before, needs.changes.result".
Measured first, with a two-job probe: `act -j b` runs `a` when `b` needs
it, so C3's per-job act invocation ran every dependency twice and an
aggregator replayed the whole workflow; act logs GITHUB_OUTPUT writes as
`::set-output:: k=v`; `github.event.*` is "" under a declared event.
- Job outputs on both engines. Native steps get a GITHUB_OUTPUT file
(k=v and k<<EOF); act's set-output lines are read back. A job's
`outputs:` resolves from `steps.<id>.outputs.<k>` and dependents read
`needs.<job>.outputs.<name>` in `if:` and `run:`. Declared-but-never-
written is refused, not blanked. Jobs with outputs are never cached.
- `needs.*` / `steps.*` deferred at DAG-build time, filled in per step.
- `github.event.*` is "" with a declared event; a native blocker without.
- act receives a derived single-job workflow (temp file, -W outside the
tree): `needs` and `if:` removed, `needs.*` spelled out from C3's own
results and outputs. Unrecorded references are reported before act runs.
- A cached runner image is no longer "pulled" (issue #173's auth trap).
- Each act run gets a free artifact-server port; two runs on one box
collided on 34567 and the second died before its first step.
Live on drknowhow/Yep's test workflow: 6/6 jobs ran, shards green, the
aggregator got real results. Verdict FAIL for an honest reason (a smoke
needs java; the act image has none). tests: 4020 passed; the codex
transport test fails on untouched main too (environment).
Claude-Session: https://claude.ai/code/session_019n1sv7vnKeRjwHMLmA7Tv2
…ved the job its gate reads `--job smokes` deselected `changes`, then `if: needs.changes.outputs.code == 'true'` was unjudgeable and the job came back UNSUP. A selector (and a rerun) now brings the transitive `needs` closure along; the rest stays DESELECTED and the verdict stays PARTIAL. Measured live: `--job smokes` on Yep's workflow now runs `changes` (1.3s, code=true) then `smokes`. Claude-Session: https://claude.ai/code/session_019n1sv7vnKeRjwHMLmA7Tv2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Yep's test workflow (drknowhow/Yep #1509: a
changesgate, three shards onif: needs.changes.outputs.code == 'true', an aggregator reading${{ needs.<job>.result }}) came back fromc3_ciwith every jobUNSUP: "unresolved expression(s): github.event.before, needs.changes.result". That shape is most real workflows.Measured before writing
Two-job probe, act 0.2.89, Docker Desktop 29.6.2:
act -j brunsafirst whenbneeds it. C3 runs jobs one-jat a time, so every dependency ran twice and an aggregator replayed the whole workflow inside its container.::set-output:: k=v(no step id).github.event.*is "".-Woutside the working tree.What
GITHUB_OUTPUTfile (k=v,k<<EOF). act: set-output lines read back.outputs:resolves viasteps.<id>.outputs.<k>; dependents getneeds.<job>.outputs.<name>inif:andrun:. Declared-but-never-written is refused, not blanked. Jobs with outputs skip the verdict cache.needs.*/steps.*deferred at DAG-build time, filled in per step at run time (late_substitute).github.event.*is "" under a declared event (what GitHub and act do); a native blocker without one.secrets.*/vars.*still block both engines.-W): this job only,needsandif:stripped,needs.*spelled out from C3's recorded results/outputs. An unrecorded reference is reported before act starts.docker image inspectfirst) — closes the issue c3_ci: act image-pull auth failure is reported as a job failure with 'unparsed' output #173 trap where act's stored credential turns a public image on disk into an auth failure.unparsed.Proof
tests/test_ci_dag_ownership.py: 19 tests (classification, derived workflow, log outputs, native outputs flow, pull policy, port).4020 passed, 9 skipped(the codex transport test fails on untouched main too — environment). The act integration test now passes while another act run holds 34567.changesalone in its container (code=truecaptured), shards 517s/362s/277s green, the aggregator handedchanges=success shards=success smokes=failure. Verdict FAIL, honestly: one smoke needsjava, present on GitHub's runner and absent from the act image. Fix for that goes to the Yep repo.Not in this PR
catthehacker/ubuntu:act-latesttoday). Cheap to add if wanted.https://claude.ai/code/session_019n1sv7vnKeRjwHMLmA7Tv2