Files
redwoodjs__local-ci/packages/cli/compatibility.md
2026-05-19 14:38:33 +02:00

57 KiB

YAML Compatibility

Agent CI aims to run real GitHub Actions workflows locally. The table below shows current support against the official workflow syntax.

✅ = Supported   ⚠️ = Partial   ❌ = Not supported   🟡 = Ignored (no-op)   🚫 = Not planned

Workflow-Level Keys

Key Status Notes
name ✅
run-name 🟡 Parsed but not displayed anywhere
on (push, pull_request) ✅ Branch and path filters are evaluated when using --all
on.<event>.branches / branches-ignore ✅ Branch-name glob filters; --all only runs workflows whose filter matches the current branch (main for non-PR runs).
on.<event>.paths / paths-ignore ✅ Path-pattern filters. With --all, a workflow is skipped if none of the changed files match paths: or all of them match paths-ignore:.
on.<event>.tags / tags-ignore ❌ Tag-pattern filters for push events are not evaluated — --all treats a tagged run as an untagged push. Workflows gated only by tag patterns will run when you don't expect them to.
on.<event>.types 🟡 Activity-type filters (e.g. pull_request.types: [opened, synchronize]) are parsed but not applied — --all runs the workflow regardless of event sub-type.
on.workflow_dispatch.inputs 🟡 workflow_dispatch itself is not simulated, so its declared inputs are parsed but unreachable — the CLI cannot inject them.
on.workflow_call.inputs.* (type / required / default) ✅ Reusable workflows resolve caller-side with: values against the callee's inputs: declarations, honoring default: when a caller omits an input.
on.workflow_call.outputs.*.value ✅ Reusable-workflow callees can expose outputs.<name>.value: ${{ jobs.<id>.outputs.<name> }}, and callers read them the same way as a normal needs.*.outputs.*.
on (schedule, workflow_dispatch) 🟡 Accepted without error, but Agent CI does not simulate event triggers — workflows must be run manually
on (workflow_call) ✅ Local reusable workflows (uses: ./.github/workflows/...) are inlined into the caller's dependency graph. Remote refs are fetched from GitHub (requires --github-token or AGENT_CI_GITHUB_TOKEN). inputs:/outputs: passing and nested reusable workflows are supported
on (other events) 🟡 Covers every GitHub-Actions webhook event Agent CI does not simulate (issues, issue_comment, label, pull_request_review, release, registry_package, status, deployment, deployment_status, fork, watch, star, discussion, milestone, project, repository_dispatch, check_run, check_suite, workflow_run, etc.). The parser accepts the event name but the run is not triggered — the workflow only ever runs when invoked manually via pnpm agent-ci run.
env ✅ Workflow-level env is propagated to all steps
defaults.run.shell ✅ Non-bash shells (sh, python, pwsh) are invoked via a parse-time heredoc wrap because the runner ignores inputs.shell for script steps. bash is the default.
defaults.run.working-directory ✅ Passed through to the runner
permissions 🟡 Accepted but not enforced — the mock GITHUB_TOKEN has full access
concurrency 🚫 Concurrency groups are a GitHub-side queuing and cancellation mechanism. Agent CI has no persistent server to track group state across runs, so this cannot be implemented locally

Job-Level Keys

Key Status Notes
jobs.<id> ✅ Multiple jobs in a single workflow
jobs.<id>.name ✅
jobs.<id>.needs ✅ Jobs are sorted topologically into dependency waves
jobs.<id>.if ✅ success(), failure(), always(), cancelled(), ==/!=, &&/||, !, needs.*.outputs.*, needs.*.result
jobs.<id>.runs-on ⚠️ ubuntu-* / linux (and unspecified self-hosted / custom labels) run in a Linux container — the default image is minimal, see runner-image.md to add system tools. macos-* runs in a real macOS VM on Apple Silicon hosts with tart + sshpass installed; other hosts skip the job with a reason. windows-* is not yet supported and skips on every host. When a label declares a larger runner spec (e.g. ubuntu-latest-8-cores) than the local machine provides, the job is tagged degraded and a warning is printed before it starts — execution is not blocked, only annotated.
jobs.<id>.environment 🟡 Accepted but not enforced — environment protection rules are GitHub-side only. Object form (environment.name / environment.url) is also accepted and ignored.
jobs.<id>.permissions 🟡 Accepted but not enforced — job-level permission scopes have no effect because the mock GITHUB_TOKEN has full access to the local API emulation.
jobs.<id>.env ✅
jobs.<id>.defaults.run ✅ shell and working-directory
jobs.<id>.outputs ✅ Resolved after each job completes and accumulated across dependency waves
jobs.<id>.timeout-minutes ❌ Not implemented. Agent CI's pause-on-failure model is the intended way to handle long-running steps — a hard timeout would destroy the container state that makes local debugging possible
jobs.<id>.continue-on-error ❌ Not implemented. Agent CI pauses on failure so you can inspect and fix the container in place; continue-on-error would skip past failures and discard that debugging opportunity
jobs.<id>.concurrency 🚫 See workflow-level concurrency above
jobs.<id>.container ⚠️ Short and long form; image, env, ports, volumes honored. Within options:, only --env/-e and --label/-l are forwarded to the runner container; other Docker flags (--privileged, --user, --network, --cap-add, --workdir, etc.) are silently ignored because they tend to clash with agent-ci's own container orchestration.
jobs.<id>.container.credentials ❌ Registry username / password credentials for pulling a private container image are not parsed. Private images fail to pull unless the host's docker already has credentials for the registry (e.g. docker login was run out-of-band).
jobs.<id>.services ✅ Sidecar containers with image, env, ports, and options
jobs.<id>.services.*.credentials ❌ Per-service registry credentials are not parsed — see jobs.<id>.container.credentials. Private service images fail to pull unless the host's docker has credentials out-of-band.
jobs.<id>.uses (reusable workflows) ✅ Local refs (./) are expanded inline. Remote refs are fetched from GitHub (requires --github-token or AGENT_CI_GITHUB_TOKEN; public repos may work without auth). with: (inputs) and secrets: pass-through are supported
jobs.<id>.secrets 🚫 Agent CI cannot access GitHub's secret storage. Secrets are resolved from a .env.agent-ci file at the project root or from shell environment variables as fallback. --github-token / AGENT_CI_GITHUB_TOKEN auto-populates secrets.GITHUB_TOKEN. All are injected as ${{ secrets.* }} expressions

Strategy / Matrix

Key Status Notes
strategy.matrix ✅ Cartesian product of all array values is fully expanded
strategy.matrix.include ❌ Not implemented. The matrix parser only processes array-valued keys; include entries (which are objects) are silently dropped. Adding support would require post-processing the Cartesian product
strategy.matrix.exclude ❌ Not implemented — same reason as include. exclude entries are objects and are dropped by the array-only parser
strategy.fail-fast ✅ Setting fail-fast: false allows remaining matrix jobs to continue after a failure
strategy.max-parallel ❌ Not implemented. Parallelism is controlled by Agent CI's host-level concurrency limiter (based on CPU count), not per-workflow job limits

Step-Level Keys

Key Status Notes
steps[*].id ✅
steps[*].name ✅ Expression expansion in names
steps[*].if ⚠️ The condition is passed to the official runner binary, which evaluates it at runtime. Limitation: steps.*.outputs.cache-hit and similar outputs resolve to an empty string at parse time because prior steps have not yet run when the workflow is parsed
steps[*].run ✅ Multiline shell scripts with ${{ }} expression expansion
steps[*].uses ✅ Public actions are downloaded via the GitHub API
steps[*].uses (local ./) ✅ Local actions (e.g. uses: ./.github/actions/my-action) are resolved from the workspace
steps[*].uses (docker://…) ❌ Docker-image action references (uses: docker://alpine:3.19) are not resolved — agent-ci treats every uses: as a repository action. The step fails with an action-not-found error rather than running the image.
steps[*].with ✅ Expression expansion in values
steps[*].env ✅ Expression expansion in values
steps[*].working-directory ✅
steps[*].shell ✅ See defaults.run.shell above — the same heredoc-wrap mechanism applies.
steps[*].continue-on-error ❌ Not implemented — see jobs.<id>.continue-on-error above for the reasoning
steps[*].timeout-minutes ❌ Not implemented — see jobs.<id>.timeout-minutes above for the reasoning

Expressions (${{ }})

Key Status Notes
hashFiles(...) ✅ SHA-256 of matching files; supports multiple glob patterns. Descends into dotted directories (e.g. .github/) when the pattern asks for them.
format(...) ✅ Template substitution with recursive expression expansion
matrix.* ✅
secrets.* ✅ Resolved from .env.agent-ci at the project root, with shell environment variables as fallback. --github-token auto-populates secrets.GITHUB_TOKEN
vars.* ✅ Repository / organization / environment variables. Resolved from --var NAME=VALUE CLI flags or JSON supplied via --var-file <path> / --var-file -; unresolved vars.* references fail the run at pre-flight with a list of every missing name.
env.* ✅ The merged step environment (workflow-level → job-level → step-level, with step taking precedence) is available as env.* in with: inputs, run: scripts, name:, and env: value expressions. Not yet supported in if: conditions (the expression is evaluated by the runner, not the parser). Note: env.* references inside an env: block (one env var referencing another declared in the same block) are not evaluated — only the already-computed merged value is exposed.
inputs.* ✅ Reusable-workflow and (nominally) workflow_dispatch input values. Inside a called workflow, inputs.<name> resolves to the caller's with: value or the declared default:.
runner.os ✅ Linux for container-based jobs, macOS for jobs routed to the macOS VM
runner.arch ✅ X64 for container-based jobs, ARM64 for macOS VM jobs (Apple Silicon)
runner.name, runner.temp, runner.tool_cache, runner.debug, runner.environment ❌ Only runner.os and runner.arch are populated; the rest of the runner.* context resolves to an empty string. Workflows that read runner.tool_cache (common in tool-setup actions) will see an empty value — the actions themselves typically fall back to the RUNNER_TOOL_CACHE env var which the runner does set.
github.sha ✅ Real commit SHA from HEAD. When the working tree is dirty, a synthetic tree SHA is computed so that expressions that hash-seed from github.sha still differ between working-tree states.
github.repository, github.repository_owner ✅ Derived from the git remote origin URL (owner/repo and owner).
github.ref, github.ref_name, github.head_ref, github.base_ref, github.actor, github.run_id, github.run_number, and other github.* fields ⚠️ Returned as static defaults: ref_name / head_ref default to main, base_ref is empty, actor is the repo owner, run_id / run_number are 1, api_url / server_url point at Agent CI's local API emulation. Anything not in this row or one of the ones above resolves to an empty string — that covers action_path, workflow_ref, workflow_sha, triggering_actor, retention_days, secret_source, token, graphql_url, job, run_attempt, ref_protected, ref_type, env, path, and the rest of the context documented by GitHub.
github.event.* ⚠️ All event payload fields return empty strings — no real webhook event is triggered locally
strategy.job-total, strategy.job-index ✅
steps.*.outputs.* ⚠️ Always resolves to an empty string at parse time — the producing step has not run yet and the runner does not re-evaluate ${{ }} inside run: script bodies. Use needs.*.outputs.* for cross-job values instead.
steps.*.conclusion / steps.*.outcome ❌ Step result states are not exposed — neither conclusion nor outcome resolves against a meaningful value. Workflows that branch on prior-step status via these context variables will always evaluate as if the step didn't produce a result.
job.* (job.status, job.container.id, job.services.*) ❌ The per-job runtime context is not populated. Expressions that read job.status etc. resolve to empty strings.
* object-filter operator ❌ Array-of-object filtering like github.event.commits.*.author.name is not implemented — the entire expression resolves to an empty string.
needs.*.outputs.* ✅ Resolved after dependency jobs complete. The needs context is built from actual job outputs and passed into subsequent job evaluation
Boolean/comparison operators ✅ ==, !=, <, >, <=, >=, &&, ||, !, parentheses. Case-insensitive string comparison. Numeric coercion follows GitHub Actions semantics: empty string and null become 0, numeric-looking strings are parsed, anything else becomes NaN and compares false.
toJSON, fromJSON ✅ toJSON emits 2-space-indented JSON matching GitHub Actions' pretty-printed output, so hashFiles/diff uses of toJSON(x) are reproducible.
contains(search, item) ✅ Case-insensitive substring for strings; element membership for JSON arrays (e.g. contains(fromJSON('["a","b"]'), 'b')). The * object-filter form (contains(labels.*.name, 'bug')) is not supported — see the * object-filter row.
startsWith(searchString, searchValue) ✅ Case-insensitive.
endsWith(searchString, searchValue) ✅ Case-insensitive.
join ✅ Joins JSON arrays with a separator (default , )
success(), failure(), always(), cancelled() ⚠️ success() / failure() / always() are evaluated against the accumulated job status in dependency waves. cancelled() always returns false locally — there is no cancellation signal for Agent CI to observe, so steps gated by if: cancelled() never run.
Type literals (true, false, null, numbers) ✅ Boolean, null, and numeric literals supported in expressions

GitHub API Features (DTU Mock)

Key Status Notes
Action downloads ✅ Action tarballs are resolved and downloaded from github.com
actions/cache ⚠️ Cache is stored on the local filesystem via bind-mount, giving ~0 ms round-trip on cache hits. Scoping is by cache key only — GitHub's ref-based isolation (a branch cannot read another branch's cache, default branch is a fallback) is not implemented. If two workflows on different refs share a key they share the cache.
actions/checkout ✅ The workspace is rsynced into the container with clean: false to preserve local changes
actions/setup-node, actions/setup-python, etc. ✅ Tool setup actions run natively inside the runner container
actions/upload-artifact / download-artifact ✅ Artifacts are stored on the local filesystem
GITHUB_TOKEN ⚠️ Not injected automatically. Pass --github-token or set AGENT_CI_GITHUB_TOKEN to make it available as secrets.GITHUB_TOKEN. GitHub API calls from the runner are answered locally by Agent CI's API emulation layer. OIDC ID-token issuance (permissions.id-token: write → federated cloud auth) is not implemented — actions that exchange the OIDC token with AWS/GCP/Vault will fail.
Workflow commands (::set-output::, ::error::, etc.) ✅ Handled by the official runner binary