Single job on macos-14, workflow_dispatch-only. Confirms the
GitHub-hosted macOS runner is healthy: OS info, toolchain sanity.
CI-only change — no changeset needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When two `agent-ci-dev` runs execute in parallel, each nested CLI
process starts in its own isolated filesystem and allocates the same
sibling container name (`agent-ci-1`). The pre-spawn
`docker rm -f <name>` then kills the other nested run's sibling.
Include the outer container's hostname in the name prefix when
`/.dockerenv` is present so sibling names stay unique across
concurrent nested runs.
Also update smoke-docker-buildx.yml to look for nested-run logs under
both `/tmp/agent-ci` (GitHub-hosted runner) and
`$GITHUB_WORKSPACE/packages/cli/.agent-ci` (nested under
agent-ci-dev), since the nested working dir differs in each case.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
During pnpm run version, scripts/release-closes.mjs captures
Closes|Fixes|Resolves #N references from pending changesets into
.release-closes.json, pairs each with the PR that added the
changeset, and rewrites the keywords to Refs #N so the 'Version
Packages' PR does not close them on merge. After the release
workflow publishes, a new step closes each captured issue with
the comment 'Closes Issue #N via PR #M.'
* test: smoke reproducer for #257 docker/setup-buildx-action
* chore: retrigger CI
* ci: switch smoke to pull_request so it triggers from the PR branch
* ci(smoke): dump agent-ci step logs + upload as artifact on failure
* fix: use MAYBE_SUDO to chmod bind-mounted docker socket (#257)
On native Linux Docker the socket is owned root:docker 0660. The runner
user isn't in the host's docker group, so the existing plain 'chmod 666
/var/run/docker.sock' silently failed — leaving every subsequent Docker
client call (docker/setup-buildx-action@v4, docker login, compose, buildx)
to fail with 'permission denied while trying to connect to the docker
API'. Escalating via MAYBE_SUDO matches the surrounding entrypoint ops.
* ci(smoke): assert against Verify-buildx.log, not top-level agent-ci stdout
---------
Co-authored-by: Peter Pistorius <peter@redwoodjs.com>
Two bugs in the smoke test, both making it fail every run since #260:
1. The header comment contained `${{ vars.FOO }}` inside backticks.
extractVarRefs() is a text-level regex scan and doesn't understand
YAML comments, so the outer preflight falsely required `--var FOO`
before the workflow would even start. Rephrase the comment to just
`vars.FOO`.
2. The heredoc that writes the inner test.yml used
`env: API_URL: ${{ vars.API_URL }}`. GitHub Actions' template engine
processes `${{ ... }}` in every `run:` script regardless of shell
heredoc quoting (`<< 'WF'` doesn't protect against it), so the
expression was being substituted at outer-run time — stripping the
vars.X references the inner agent-ci needs to exercise preflight.
Result: every Case 1 (no --var flags) run saw test.yml with no
refs, preflight passed, the workflow ran with empty env, and the
assertion against 'https://api.example.com' failed.
Fix: build the literal '${{' at shell runtime from non-triggering parts
(D='$' + "{{") so the outer template engine never sees the pattern in
source. Switch heredoc to unquoted (<<WF) so the shell variable expands,
and escape \/\ to keep them literal for the inner
job's shell.
Validated via `pnpm agent-ci-dev run --workflow smoke-vars-preflight.yml
--var API_URL=https://api.example.com --var DEPLOY_ENV=production`:
all three cases now pass (14s).
No changeset: workflow-only change.
* kindling: start task [skip ci]
* harness: save uncommitted work from Analyst (cycle 1) [skip ci]
* Fix misleading 401/403 error hint when GitHub token is already provided
When fetching a remote workflow from a private repo with a token supplied,
a 401 or 403 from GitHub now says the token may be invalid, expired, or
lack the required 'repo' scope — instead of repeating instructions to use
--github-token that the user already followed.
Added two tests covering the with-token 401 and 403 paths, which were
previously uncovered.
Fixes#255
* harness: save uncommitted work from Developer (cycle 2) [skip ci]
* harness: save uncommitted work from Reviewer (cycle 3) [skip ci]
* harness: pre-DONE sweep [skip ci]
* kindling: ready for CI
* harness: save uncommitted work from Analyst (cycle 1) [skip ci]
* harness: save uncommitted work from Developer (cycle 2) [skip ci]
* harness: save uncommitted work from Reviewer (cycle 3) [skip ci]
* harness: save uncommitted work from Developer (cycle 4) [skip ci]
* harness: save uncommitted work from Reviewer (cycle 5) [skip ci]
* harness: pre-DONE sweep [skip ci]
* fix: handle 404 on remote workflow fetch as private-repo auth failure
GitHub's Contents API returns HTTP 404 (not 401/403) when a caller lacks
access to a private repository — this is intentional to avoid leaking repo
existence. The previous error hint only covered 401/403, so users hitting
the most common private-repo failure modes (missing token, classic PAT
without 'repo' scope, fine-grained PAT without 'contents: read', unauthorized
SSO PAT) saw a bare "HTTP 404" with no guidance.
Unify hint generation into buildAuthHint() covering 401/403/404 × with/
without token. Hints now include explicit gh auth login instructions and
spell out the token-insufficient scenarios.
Verified end-to-end against a private repro repo (peterp/agent-ci-private):
no-token → 404 path now emits full auth guidance, invalid-token → 401 path
emits token-insufficient hint, valid-token → resolves.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: smoke test for private-repo remote workflow fetch (#261)
Adds .github/workflows/smoke-remote-private-workflow.yml — the end-to-end
verification workflow used to reproduce and prove the #261 fix, now preserved
as a regression-guard smoke test.
It references a reusable workflow in a private repo
(peterp/agent-ci-private/.github/workflows/lint.yml@main). Under agent-ci this
exercises prefetchRemoteWorkflows against the GitHub Contents API with the
supplied token, covering the success path (HTTP 200 from a private repo when
a valid token is attached) that the unit tests in
packages/cli/src/workflow/remote-workflow-fetch.test.ts don't cover.
Verified locally with 'pnpm agent-ci-dev run --workflow … --github-token':
Status: ✓ 2 passed (2 total)
Duration: 9s
Without a token, the same smoke surfaces the improved #261 hint chain verbatim
(HTTP 404 → 'The repository or ref was not found …' → auth instructions).
---------
Co-authored-by: kindling <kindling@harness>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* kindling: start task [skip ci]
* Add behavioral specs and tests for vars context support (SPEC-V-001 through SPEC-V-015)
Tests cover loading vars from dotenv file, env-var allowlist fallback, file-over-env
precedence, extracting ${{ vars.X }} refs from workflow YAML (whole-file and job-scoped),
validating that all referenced vars are supplied (error names missing keys and config file
path), and that workflows with no vars refs run without any config present. Tests mirror
the existing secrets test conventions and will pass once the implementation (Track A) is
in place.
* harness: save uncommitted work from Developer+QA (cycle 1) [skip ci]
* harness: save uncommitted work from Reviewer (cycle 2) [skip ci]
* harness: save uncommitted work from Developer (cycle 3) [skip ci]
* harness: save uncommitted work from Reviewer (cycle 4) [skip ci]
* Add vars context loading, extraction, validation, and expression substitution
- loadMachineVars in config.ts reads .env.agent-ci.vars with the same
KEY=VALUE parsing and envFallbackKeys allowlist as loadMachineSecrets
- extractVarRefs in workflow-parser.ts scans workflow YAML for
${{ vars.NAME }} references using the same pattern as extractSecretRefs
- validateVars throws a descriptive error listing missing var names and
the expected file path, mirroring validateSecrets
- resolveExprAtom gains a vars? 7th param and a vars. prefix case after
the secrets. case; all 11 internal evaluateExprValue calls for
fromJSON/toJSON/format/contains/startsWith/endsWith/join updated
- evaluateExprValue gains vars? 7th param threaded through all 6
recursive calls (||, &&, !, comparisons, parens) and the
resolveExprAtom leaf at line 606
- expandExpressions and parseWorkflowSteps each gain vars? as optional
7th param; all internal expandExpressions call sites updated
* Wire vars context into all three CLI call sites
- Import loadMachineVars, extractVarRefs, validateVars from their
respective modules
- Single-job path: extract refs, load vars, validate, thread vars
through expandExpressions (inputsContext expansion) and
parseWorkflowSteps
- buildJob: extract refs, load vars, validate (no parseWorkflowSteps
call here, consistent with how secrets are handled)
- runJob: extract refs, load vars, validate, thread vars through
resolveInputsForJob and parseWorkflowSteps
- resolveInputsForJob: accepts vars? as 4th optional param and threads
it through its expandExpressions call
* Add Vars section to README and minor-bump changeset
README documents .env.agent-ci.vars file and shell env fallback,
explaining that vars are non-sensitive and can be committed.
Changeset bumps both @redwoodjs/agent-ci and dtu-github-actions minor
as they are in the same fixed versioning group.
* Fix loadMachineVars test helper to write .env.agent-ci.vars
The QA-authored loadMachineVars tests used .env.agent-ci as the
fixture file name, but loadMachineVars reads from .env.agent-ci.vars
per the approved RFC. Updated the writeEnvFile helper and test
description in the loadMachineVars block to match the correct file.
* harness: save uncommitted work from Developer (cycle 5) [skip ci]
* Replace file+env vars sourcing with --var CLI flag
Address PR feedback: workflow vars (${{ vars.FOO }}) are now supplied
exclusively via the repeatable --var KEY=VALUE CLI flag. Removes the
.env.agent-ci.vars file and the shell-env fallback so workflow vars
don't overload shell env vars. Missing vars fail fast with an error
listing the --var flags needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Pre-flight missing vars across all workflows before setup
Scan every workflow for ${{ vars.* }} refs at the start of the run
and fail with one combined message listing every missing var across
every file. Happens before Docker prefetch and workspace setup, so
users find out immediately instead of waiting through bootstrap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add smoke test for --var pre-flight
Covers three cases against a temp workflow referencing vars.API_URL
and vars.DEPLOY_ENV:
1. No --var flags → exit 1, error lists both vars with --var syntax
2. Partial fill → error lists only the still-missing var
3. All vars supplied → run proceeds and the job succeeds
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: kindling <kindling@harness>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: setup-node "Bad credentials" + stale-toolcache hint (#171, #249)
Route setup-node's versions-manifest fetch through the DTU so it no
longer 401s against real api.github.com, and surface an actionable
hint when a stale /opt/hostedtoolcache bind mount blocks tar extract.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: unit + smoke coverage for setup-node manifest mock
Adds unit tests for rewriteSetupNodeTarball and for the cached tree/blob
route paths, plus a nested-agent-ci smoke workflow that fails if
setup-node logs "Bad credentials" (the symptom of the manifest mock
breaking and falling back to nodejs.org).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add smoke test reproducing Cypress cache permission failure (#234)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make /home/runner/.cache world-writable so Cypress can install (#234)
The Playwright bind mount causes Docker to create /home/runner/.cache as
root, so Cypress postinstall fails with EACCES when it tries to mkdir
/home/runner/.cache/Cypress. chmod 1777 in the entrypoint fixes it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: smoke repro for setup-bun cache collision (#212)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: setup-bun fails in bun projects (#212)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add smoke repro for missing cc in runner image
Minimal smoke workflow that calls `command -v cc` and compiles a
trivial C program. Reproduces a cargo/Rust workflow failure where
`linker 'cc' not found` breaks builds under agent-ci-dev because
ghcr.io/actions/actions-runner:latest does not ship build-essential.
CI-only change (no packages/ code modified) — no changeset.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: pluggable runner image via .github/agent-ci.Dockerfile (#208)
agent-ci now discovers a user-provided Dockerfile at
.github/agent-ci.Dockerfile (or .github/agent-ci/Dockerfile for builds
with a COPY context), hashes its contents, builds it locally via
`docker build`, and uses the resulting agent-ci-runner:<hash> tag as
the default runner image. Edits trigger automatic rebuilds; identical
contents reuse the cached image.
This closes the gap where the minimal ghcr.io/actions/actions-runner
image lacks build-essential, python3, and other toolchains that
GitHub's hosted ubuntu-latest VM ships preinstalled. Workflows green
on GitHub but failing locally with `linker 'cc' not found` can now
opt into a richer image by dropping a 5-line Dockerfile into .github/.
Resolution order (highest wins):
1. per-job `container:` (unchanged)
2. AGENT_CI_RUNNER_IMAGE env var
3. .github/agent-ci/Dockerfile (directory form, supports COPY)
4. .github/agent-ci.Dockerfile (simple form, empty context)
5. ghcr.io/actions/actions-runner:latest (default)
Also adds an error-hint heuristic: when a step fails with "command not
found" for cc/gcc/make/python3/pkg-config and the user is still on the
default image, the failure summary includes a paste-ready Dockerfile
pointing at the fix. Docs at packages/cli/runner-image.md.
Dogfoods the feature by shipping .github/agent-ci.Dockerfile for this
repo, which makes smoke-cc-compiler.yml (the issue #208 reproduction)
pass end-to-end.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: announce first-run runner image build
Print a one-time stderr notice before `docker build` fires so users
understand why the first run pauses for ~60s. The message lives in
stderr so log-update can't clobber it and stays in scrollback after.
Docs updated to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: clarify that container: runs verbatim, no Dockerfile layering
Spell out explicitly that a job-level container: directive bypasses
.github/agent-ci.Dockerfile entirely — agent-ci does not layer the
Dockerfile on top. This preserves the "same YAML runs locally and on
GitHub" parity promise.
Also document the two recommended ways to combine a custom base
image with agent-ci's extras.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous approach symlinked workspace/node_modules → /tmp/node_modules,
which broke esbuild path resolution in pnpm monorepos. Tools like rwsdk's
directive scanner resolved paths through the symlink incorrectly, producing
paths like apps/web/tmp/node_modules/.pnpm/... instead of /tmp/node_modules/.pnpm/...
Mount the warm cache as a Docker bind mount directly at the workspace
node_modules path. This makes the filesystem layout identical to production
GitHub Actions. The original EBUSY concern is already mitigated by the git
shim (blocks git clean) and checkout patching (clean: false).
Adds a smoke test exercising esbuild resolution through pnpm workspace
symlinks to prevent regression.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: mount warm-modules at /tmp/node_modules for pnpm TypeScript compat
TypeScript resolves @types/* by walking up from realpath(). With pnpm's
.pnpm virtual store symlinked to /tmp/warm-modules, the upward walk never
finds a node_modules directory. Mounting at /tmp/node_modules instead means
TypeScript's walk from /tmp/node_modules/.pnpm/... naturally finds
/tmp/node_modules/@types/.
Closes#176
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: preserve git-tracked symlinks in workspace snapshot
copyWorkspace was using cp/fs.cpSync which dereference symlinks, causing
git mode 120000 entries to be dropped from the workspace commit. Now we
check lstat() before copying and recreate symlinks via symlinkSync.
Closes#178
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add pnpm symlink smoke test reproducing #176 and #178
Adds a committed fixture at .github/fixtures/pnpm-symlinks/ with a
git-tracked symlink (mode 120000) and a TSX file that requires
@types/react resolution through pnpm's .pnpm virtual store.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Wire `with:` inputs from caller workflows to called workflows via
`inputs.*` context, resolve `on.workflow_call.inputs` defaults, and
propagate `on.workflow_call.outputs` back so downstream jobs can
access `needs.<callerJobId>.outputs.*`. Also fixes intra-workflow
`needs.*` resolution for composite job IDs.
Adds smoke test, unit tests, /validate command, and CLAUDE.md agent rules.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace the nesting guard in expandReusableJobs with recursive expansion,
cycle detection, and a depth limit of 4 matching GitHub Actions' max.
Closes#167
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support local reusable workflows (uses: ./.github/workflows/...)
Inline called workflow jobs into the caller's dependency graph so that
`jobs.<id>.uses` with local refs works end-to-end. Includes dependency
rewiring, smoke tests, runner seed resilience fix, boot-failure log
surfacing, and dev script improvements.
Closes#149
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update website compatibility matrix for reusable workflows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add section descriptions to website compatibility matrix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add descriptions to compatibility matrix legend items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add 'not planned' status icon to compatibility matrix
Distinguish architectural limitations (concurrency groups, GitHub
secrets) from features that could be implemented in the future.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support remote reusable workflows (uses: owner/repo/path@ref)
Pre-fetch remote workflow YAML from GitHub Contents API before expansion,
cache SHA refs forever and always re-fetch mutable tag/branch refs. Also
changes missing workflow files from warn+skip to hard failure.
Closes#165
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: split monolithic smoke-tests.yml into discrete workflow files
Each smoke test area now has its own workflow file for isolated failure
visibility: matrix, artifacts/services, cross-job outputs, job conditions,
reusable workflows (local + remote), and binary smoke.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: fix formatting in .docs/ files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: clarify agent-ci-dev.sh is for local development only
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: support pull_request_target in workflow relevance check
Treat pull_request_target identically to pull_request in isWorkflowRelevant(),
applying the same branch and paths filter logic. This fixes agent-ci --all
skipping workflows that use pull_request_target.
Closes#106
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Docker container name collision when running multiple workflows via --all
Pre-allocate unique base run numbers per workflow before launching them
in parallel, so job suffixes (-j1, -j2, -j3) never produce duplicate
container names across concurrent workflows.
Closes#125
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolveDtuHost test and smoke-tests build
Clear AGENT_CI_DTU_HOST in resolveDtuHost tests so the bridge gateway
test exercises the correct code path when running inside agent-ci's
Docker containers (which inject AGENT_CI_DTU_HOST).
Scope binary-smoke build to @redwoodjs/agent-ci... to avoid building
apps/website which fails due to warm-modules symlink resolution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(website): add Copy Agent Instructions button to hero and quick start sections
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove retry-proof failure test from CI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 'opened' to pull_request_target types so checks fire immediately
when a PR is created by an owner/member/collaborator. External
contributors still require the 'safe-to-run' label (handled by the
existing if: condition on each job).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the 20-line tail (with "Last output:" label and indentation)
with the full content of the per-step log file already captured at
failedStepLogPath. Falls back to lastOutputLines when no log file
exists (e.g. container crash).
Adds reporter.test.ts to prove the behaviour, and an end-to-end check
in tests.yml that runs the retry-proof workflow and asserts the step
output appears in stdout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Build and execute the released binary as part of smoke tests: verify shebang is correct, run built binary directly via node, and run built binary via npx (simulates npx install). Catches shebang issues, broken imports, and bin-field misconfigurations before publishing.
pnpm -r test was including the root workspace, whose test script
is itself `pnpm -r test`, causing every test to run twice in parallel.
Both runs share /tmp/dtu_cache, and when one run's beforeEach cleanup
deleted temp files the other run's server was using → ENOENT.
Add --include-workspace-root=false to prevent the root workspace from
being matched by recursive test runs.
- Fix extractStepOutputs path: _temp/_runner_file_commands/ (not root)
- DTU outputs endpoint now captures and persists outputs.json
- local-job.ts reads outputs.json from log dir instead of work dir
- Add smoke test jobs for cross-job outputs and if:always()
All 279 tests pass.
- Rename package from 'machinen' to 'agent-ci' across all source, tests, scripts, configs, and docs
- Rename CLI binary from 'machinen' to 'agent-ci'
- Rename .machinen → .agent-ci, .env.machinen → .env.agent-ci, MACHINEN_* → AGENT_CI_*
- Rename --runner flag to --name/-n (--runner still accepted for compat)
- agent-ci-dev script now builds DTU before running CLI
- Extract YAML compatibility tables to cli/compatibility.md
- Expand CLI usage docs with per-command flag tables
- Document DOCKER_HOST support for remote Docker
Minimal vitest test that reads a fixture file and asserts its value,
designed to validate the pause-on-failure retry mechanism end-to-end.
- .github/workflows/retry-proof.yml
- experiments/retry-proof/ (package, test, fixture)
- Added retry-proof to pnpm workspace