YAML Compatibility

May 19, 2026 · View on GitHub

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

KeyStatusNotes
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-ignoreBranch-name glob filters; --all only runs workflows whose filter matches the current branch (main for non-PR runs).
on.<event>.paths / paths-ignorePath-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-ignoreTag-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.*.valueReusable-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.
envWorkflow-level env is propagated to all steps
defaults.run.shellNon-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-directoryPassed 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

KeyStatusNotes
jobs.<id>Multiple jobs in a single workflow
jobs.<id>.name
jobs.<id>.needsJobs are sorted topologically into dependency waves
jobs.<id>.ifsuccess(), 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.runshell and working-directory
jobs.<id>.outputsResolved after each job completes and accumulated across dependency waves
jobs.<id>.timeout-minutesNot 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-errorNot 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.credentialsRegistry 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>.servicesSidecar containers with image, env, ports, and options
jobs.<id>.services.*.credentialsPer-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

KeyStatusNotes
strategy.matrixCartesian product of all array values is fully expanded
strategy.matrix.includeNot 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.excludeNot implemented — same reason as include. exclude entries are objects and are dropped by the array-only parser
strategy.fail-fastSetting fail-fast: false allows remaining matrix jobs to continue after a failure
strategy.max-parallelNot implemented. Parallelism is controlled by Agent CI's host-level concurrency limiter (based on CPU count), not per-workflow job limits

Step-Level Keys

KeyStatusNotes
steps[*].id
steps[*].nameExpression 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[*].runMultiline shell scripts with ${{ }} expression expansion
steps[*].usesPublic 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[*].withExpression expansion in values
steps[*].envExpression expansion in values
steps[*].working-directory
steps[*].shellSee defaults.run.shell above — the same heredoc-wrap mechanism applies.
steps[*].continue-on-errorNot implemented — see jobs.<id>.continue-on-error above for the reasoning
steps[*].timeout-minutesNot implemented — see jobs.<id>.timeout-minutes above for the reasoning

Expressions (${{ }})

KeyStatusNotes
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.osLinux for container-based jobs, macOS for jobs routed to the macOS VM
runner.archX64 for container-based jobs, ARM64 for macOS VM jobs (Apple Silicon)
runner.name, runner.temp, runner.tool_cache, runner.debug, runner.environmentOnly 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.shaReal 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_ownerDerived 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.*.outcomeStep 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 operatorArray-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, fromJSONtoJSON 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.
joinJoins 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)

KeyStatusNotes
Action downloadsAction 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/checkoutThe 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-artifactArtifacts 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