Commit Graph

81 Commits

Author SHA1 Message Date
github-actions[bot] 7731f9cbdc chore: version packages (#185)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-10 11:58:18 +02:00
Peter Pistorius 1f24fecc14 fix: make GitHub auth opt-in for remote workflow fetching (#183)
* fix: make GitHub auth opt-in for remote reusable workflow fetching

Remove implicit credential resolution (gh auth token / GITHUB_TOKEN fallback)
that could silently send user credentials. Auth is now opt-in via
--github-token CLI flag or AGENT_CI_GITHUB_TOKEN env var.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add changeset for opt-in GitHub auth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add --github-token flag and AGENT_CI_GITHUB_TOKEN to docs

Update CLI reference tables, environment variables section, and
compatibility matrix to reflect opt-in GitHub auth for remote
reusable workflows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make commit status posting opt-in via --github-token

Gate postCommitStatus() on the githubToken parameter. When no token
is provided, print a warning instead of silently using implicit
gh CLI credentials.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add --commit-status flag to opt in to posting GitHub commit statuses

Commit status posting now requires both --commit-status and
--github-token. Without --commit-status, no warning is printed
and no status is posted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add tests for 403 error hint and public repo fetch without auth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 11:55:26 +02:00
github-actions[bot] b90191b477 chore: version packages (#181)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-10 10:53:40 +02:00
Peter Pistorius 912ed83465 fix: preserve git-tracked symlinks in workspace snapshot (#179)
* 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>
2026-04-10 10:30:28 +02:00
Peter Pistorius 8fbe36d076 fix: mount warm-modules at /tmp/node_modules for pnpm TypeScript compat (#177)
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>
2026-04-10 08:40:23 +02:00
Peter Pistorius f660c11a4d feat: support composite action step outputs and changed-files actions (#174)
* feat: support composite action step outputs and push event context

Enable workflows that use composite actions with cross-step output
references (e.g. Khan/actions@get-changed-files + filter-files) by:

- Preserving steps.* expressions for runner-time evaluation instead
  of expanding them to empty string at parse time
- Using correct BasicExpression token type (type 3, not 6) matching
  the runner's wire format
- Adding ContextName to steps so the runner can resolve
  steps.<id>.outputs.<name> expressions
- Adding push event before/after SHAs and GITHUB_API_URL so
  changed-files actions can call the compare commits API
- Implementing compare commits and list-PRs-for-commit mock endpoints
  in the DTU server using git diff on the original repo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve symlinks in agent-ci-dev.sh so it works when invoked via symlink

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 23:02:21 +02:00
Peter Pistorius 789e403c74 feat: support reusable workflow inputs/outputs passing (#173)
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>
2026-04-09 21:08:59 +02:00
Peter Pistorius cf31ce145a feat: support nested reusable workflows up to 4 levels deep (#172)
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>
2026-04-09 20:13:29 +02:00
Peter Pistorius 6b7a95b180 feat: support local composite actions (#169)
* feat: support local composite actions (`uses: ./.github/actions/...`)

Set RepositoryType to "self" for local action references so the runner
resolves them from the workspace instead of trying to download a tarball.
Add defensive guard in actiondownloadinfo to skip local/empty actions.

Closes #162

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>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 17:07:31 +02:00
Peter Pistorius e9c5df5f6f feat: support reusable workflows (local and remote) (#163)
* 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>
2026-04-09 17:07:13 +02:00
Patrick Gallagher d1d8452251 Fix tcp DOCKER_HOST handling in local runner (#161) 2026-04-09 13:08:49 +02:00
Peter Pistorius 7a612b041b fix: remove duplicate error messages on workflow failure (#160)
Remove the useless try/catch wrapper in handleWorkflow so errors are
only reported once by the top-level catch handler.

Closes #154

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 23:10:35 +02:00
Peter Pistorius 89d1483a7e feat: auto-detect Docker socket path when DOCKER_HOST is not set (#159)
* feat: auto-detect Docker socket path when DOCKER_HOST is not set

Resolves #158. Adds resolveDockerSocket() which finds the Docker daemon
socket by checking DOCKER_HOST, resolving symlinks at /var/run/docker.sock,
parsing `docker context inspect`, and falling back to well-known macOS
provider paths (OrbStack, Docker Desktop, Colima, Lima). Throws with
actionable guidance when no socket is found.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: lazy-init Docker socket resolution and client

Defer resolveDockerSocket() and Dockerode construction until first use
so that module import (e.g. --help) doesn't throw or shell out to
docker context inspect on machines without Docker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 23:00:57 +02:00
Peter Pistorius 81aedf33d6 fix: suppress stderr leak and support non-origin remotes (#157)
* fix: suppress stderr leak and support non-origin remotes

- Extract `getFirstRemoteUrl()` that prefers 'origin' but falls back to
  the first available remote, with stdio piped to suppress stderr
- Use `process.cwd()` instead of `PROJECT_ROOT` in `deriveGithubRepo()`
  so it detects the user's repo, not the CLI install location
- Share the helper between config.ts and cli.ts

Closes #152

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle trailing slash in remote URLs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add tests for parseRepoSlug and getFirstRemoteUrl

Extract regex parsing into exported parseRepoSlug() so both
config.ts and cli.ts share the same logic, and it's testable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: simplify getFirstRemoteUrl to single git remote -v call

One process spawn instead of up to three.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract resolveRepoSlug into config.ts with tests

Move repo detection out of cli.ts private function into a shared,
exported resolveRepoSlug() in config.ts so it's testable and
eliminates the duplicated logic between config.ts and cli.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: don't auto-detect repo at import time from process.cwd()

The config default is just a last-resort fallback. Real detection
happens via resolveRepoSlug(repoRoot) in cli.ts, anchored to the
workflow path — process.cwd() at import time was a guess that could
be wrong.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: throw when repo cannot be detected instead of using unknown/unknown

Silently defaulting to "unknown/unknown" would produce wrong API URLs,
container env vars, and directory names. Now resolveRepoSlug() throws
with a clear message pointing users to set GITHUB_REPO.

Also removes dead config.GITHUB_REPO fallbacks from runner code — the
repo slug is always resolved from the workflow path in cli.ts before
jobs are created.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: use git config instead of parsing git remote -v output

git config --get remote.<name>.url returns just the URL — no
tab-delimited line parsing needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: propagate workflow-derived repo slug back to config.GITHUB_REPO

Once resolveRepoSlug detects the repo from the workflow path, write
it back to config.GITHUB_REPO so downstream code (commit-status.ts,
runner fallbacks) sees the correct value without needing it passed
through explicitly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: env var GITHUB_REPO overrides auto-detection, not the reverse

GITHUB_REPO env var is the explicit override; auto-detection from
the workflow's git remote is the default.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: verify GITHUB_REPO env var overrides auto-detection

Tests the priority contract: env var wins over git remote detection,
auto-detection is the fallback, and missing both throws.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: make githubRepo required in CreateRunDirectoriesOpts

It's always provided by cli.ts after resolveRepoSlug. Remove the
inconsistent defensive fallbacks ("unknown-repo" vs "repo") that
were dead code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use git remote get-url instead of git config --get

git config reads from the config hierarchy (local → global → system),
so on CI runners where actions/checkout configures git globally, it
finds the parent repo's remote.origin.url even inside a fresh git init
temp dir. git remote get-url is scoped to the repo's own remotes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 22:38:33 +02:00
Peter Pistorius c7d45a24a1 fix: use DOCKER_HOST socket path for container bind mount (#156)
* fix: use DOCKER_HOST socket path for container bind mount

The Docker socket bind mount was hardcoded to /var/run/docker.sock on the
host side, ignoring DOCKER_HOST overrides. Extract the resolved socket path
and pass it into buildContainerBinds so the host side matches the actual
daemon socket.

Closes #155

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: don't run dockerSocketPath through toHostPath

The socket path is already a host path (from DOCKER_HOST or the default),
so it should not be remapped by toHostPath which translates container
paths to host paths in nested Docker scenarios.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 21:57:53 +02:00
Peter Pistorius ed4e86c41c fix: guard against undefined template.jobs in parseWorkflowSteps (#148)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 13:02:36 +02:00
Peter Pistorius 1b1c664741 fix: guard against undefined template.jobs from workflow parser (#147)
When @actions/workflow-parser cannot model certain expressions (e.g.
runner.temp in env values), it may return a template without a jobs
array. This caused a TypeError crash. Adding nullish coalescing
fallbacks lets the existing "no jobs found" / "task not found" error
paths handle these cases gracefully.

Closes #96 (issue 1 of 3).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 13:00:10 +02:00
github-actions[bot] 4de00d6948 chore: version packages (#122)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-07 15:43:50 +02:00
Peter Pistorius 8ff11e0497 test: add reproduction test for issue #126 (#132)
Adds a test that verifies resolveDtuHost() correctly uses the
AGENT_CI_DOCKER_BRIDGE_GATEWAY env var when the fs.existsSync mock
intercepts the /.dockerenv check. Verified passing both locally and
inside Docker via agent-ci.

Closes #126

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 15:22:41 +02:00
Peter Pistorius 73f6bf0685 fix: propagate job-level env into DTU Variables store (#104) (#131)
* fix: propagate job-level env into DTU Variables and add AGENT_CI_LOCAL to container env

Closes #104. Job-level `env` from the CLI payload was not propagated into
the DTU's Variables store or ContextData, so `AGENT_CI_LOCAL` was unavailable
to the runner's expression engine. Also adds `AGENT_CI_LOCAL=true` to the
Docker container env for shell access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci: re-trigger agent-ci checks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 13:44:47 +02:00
Peter Pistorius 336fb984fb fix: treat runner that never contacted DTU as failure (#130)
* fix: treat runner that never contacted DTU as failure

When the runner container exits with code 0 but never sent any timeline
entries (isBooting is still true), the job was incorrectly reported as
succeeded. Now checks !isBooting in the jobSucceeded condition.

Closes #102

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: treat runner that never contacted DTU as failure

When the runner container exits with code 0 but never sent any timeline
entries (isBooting is still true), the job was incorrectly reported as
succeeded. Extract isJobSuccessful() and add tests covering the #102
regression case.

Closes #102

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 12:54:36 +02:00
Peter Pistorius 5fadfee0eb fix: clean up all containers on signal, not just the first job's (#129)
* fix: remove stopped containers before network prune to prevent address pool exhaustion

Closes #107

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clean up all containers on signal, not just the first job's

Each job registered process.once("SIGINT") with process.exit(1) in
the handler. The first handler to fire would kill the process before
other jobs' handlers could clean up their containers, leaving them
running indefinitely.

Changed to process.on so all handlers fire, removed process.exit(1)
from per-job handlers, and added a top-level handler in cli.ts that
defers exit until after all per-job cleanup completes.

Closes #107

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 12:25:04 +02:00
Peter Pistorius 17ef34087e fix: pin runnerName before DTU seed to prevent cross-workflow job stealing (#103) (#127)
When running --all, jobs seeded without runnerName landed in the shared
generic pool where runners from other concurrent workflows could steal
them, causing single-job workflows to hang indefinitely.

- Pin job.runnerName = containerName before the /_dtu/seed call
- Move container and ephemeralDtu cleanup into finally block
- Add process.setMaxListeners(0) for parallel job EventEmitter warnings
- Add reproduction test for generic pool job stealing

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 11:16:49 +02:00
Peter Pistorius 1e9d7caa9d feat: support pull_request_target in workflow relevance check (#124)
* 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>
2026-04-07 10:16:01 +02:00
Peter Pistorius cc73a1f9c2 fix: atomic log directory allocation for concurrent runs (#123)
* fix: use atomic mkdir for concurrent log directory allocation

Closes #105

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add tests for atomic log directory allocation

Tests EEXIST collision handling, multiple consecutive collisions,
and uniqueness across many sequential calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: clear AGENT_CI_DTU_HOST in resolveDtuHost tests

The resolveDtuHost function returns early if AGENT_CI_DTU_HOST is set.
When tests run inside Docker via agent-ci, this env var is present,
causing the bridge gateway test to be bypassed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 10:03:15 +02:00
Peter Pistorius be5cacd35f fix: re-throw errors in handleWorkflow instead of swallowing them (#121)
The catch block in handleWorkflow was returning an empty array on error,
causing the CLI to exit 0 even when workflow setup failed. Now re-throws
so failures propagate and exit non-zero.

Closes #101

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 20:46:13 +02:00
github-actions[bot] 70f548cdcc chore: version packages (#118)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-06 20:17:45 +02:00
Peter Pistorius c2fe31b888 feat: action tarball disk cache, step output capture, and demo recording (#117)
* feat: action tarball disk cache, step output capture, and demo recording

- Cache action tarballs on first download and serve from disk on re-runs,
  eliminating ~30s GitHub CDN delays
- Capture step output via tee to signals dir for reliable pause-on-failure
  tail display
- Fix CLI to treat empty results as failure and skip summary print
- Add marketing demo recording setup with VHS tape and test project
- Add `agent-ci` script alias in root package.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add tests for action tarball cache, group filtering, and empty-results fix (#119)

Cover the untested changes from the tarball cache PR: action tarball proxy
cache hit/miss, download info URL rewriting with sub-path stripping, step
output group filtering, tee output capture, and CLI empty-results exit logic.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent concurrent tarball cache-miss race condition

Use an in-flight download map so concurrent requests for the same
tarball coalesce into a single GitHub fetch instead of racing on
the same tmp file, which could cause corruption or rename errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use relative path in demo.tape for portability

Replace hard-coded absolute path with a relative path so the demo
recording works on any contributor's machine.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: correct sanitized-refs test to assert 404 for slash-containing refs

The test was passing vacuously (status >= 200 matches 404). Now it
properly asserts that Polka does not bind :ref across slashes, so
slash-containing refs return 404 and do not serve cached content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 20:16:18 +02:00
Peter Pistorius f9f17fd0b9 fix: only mount relevant PM cache for detected package manager (#114)
* fix: only mount relevant PM cache based on detected package manager

Projects using npm/yarn/bun no longer get unnecessary pnpm store bind
mounts (and vice versa). Detects PM from lockfile and conditionally
creates cache dirs, bind mounts, and virtual cache patterns.

Closes #112

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: add debug log for detected PM, remove verbose bind mount log

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add PM-scoped caching integration tests

Tests createRunDirectories and buildContainerBinds for each package
manager (npm, pnpm, yarn, bun) plus the no-lockfile fallback. Each
test creates a real git repo fixture with the relevant lockfile and
verifies only the correct PM's cache dir and bind mount are produced.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 18:44:55 +02:00
Peter Pistorius acb750f346 feat: show Docker pull progress during boot (#116)
* feat: show Docker image pull progress during runner boot

Display download progress (bytes downloaded / total with percentage) as
a sub-step under "Starting runner" while pulling the container image.
Aggregates per-layer progress from the Docker API and throttles UI
updates to every 250ms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: only track Downloading events for pull progress

Extracting events reset the current byte count per layer, making
overall progress appear stuck or go backwards. Filter to only
"Downloading" status events so progress climbs steadily to 100%.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle Download complete events to reach 100%

Docker's last event per layer is "Download complete" with no
progressDetail, so layers were never marked as fully downloaded.
Now explicitly set current = total when a layer completes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* debug: log all docker pull events to diagnose stuck progress

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: track download and extraction phases separately

The progress was getting stuck because:
1. "Download complete" events fired rapidly and got throttled
2. Extraction phase was invisible — no events passed the filter

Now tracks downloading and extracting as separate phases with
independent progress maps. The UI shows "Downloading: X/Y (Z%)"
then transitions to "Extracting: X/Y (Z%)" once extraction begins.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: force UI update on download→extract phase transition

The throttle was swallowing the first "Extracting" event, leaving
the display frozen on the last download snapshot. Now flushProgress
accepts a force flag and the first extraction event always triggers
an immediate store update regardless of throttle timing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show reset hint when image pull takes over 60s

After 60s of pulling, shows a dim hint beneath the progress line:
  "Taking a while? Run: docker rmi <image>"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* debug: log logDir presence in boot node renderer

* fix: remove debug logging, shorten logDir, lower hint threshold to 30s

- Remove debug-renderer and pull flush diagnostic logging
- Shorten logDir display path to agent-ci/cli/runs/... so it stays
  visible alongside the progress line without wrapping
- Lower the "Taking a while?" hint threshold from 60s to 30s since
  Docker stalls immediately when resuming a partially cached layer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: remove reset hint from pull progress display

The docker rmi hint wasn't helpful since it doesn't fix the stalled
download (the partial blob lives inside Docker's VM, not the layer
store). Removed along with the now-unused image/startedAt fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 18:43:59 +02:00
Jökull Sólberg 3bb8525421 fix: support large monorepos and sub-path actions
Three fixes discovered while running agent-ci against a ~40K file
pnpm/turbo monorepo:

1. Sub-path actions (e.g. `actions/cache/save@v5`) produce invalid
   tarball URLs. The DTU was using `action.nameWithOwner` directly
   which includes the sub-path, producing
   `api.github.com/repos/actions/cache/save/tarball/v5` (404).
   Fix: strip to `owner/repo` before constructing the URL.

2. `copyWorkspace` uses `execSync("git ls-files ...")` with the
   default 1MB maxBuffer. Repos with 30K+ tracked files overflow
   this, causing ENOBUFS and an empty workspace in the container.
   Fix: increase maxBuffer to 100MB.

3. Post-success cleanup `fs.rmSync(containerWorkDir)` can throw
   ENOTEMPTY when the container hasn't fully released file handles.
   Fix: wrap in try/catch for best-effort cleanup.
2026-03-27 22:58:00 +00:00
github-actions[bot] 987477bae4 chore: version packages 2026-03-27 20:32:23 +00:00
Peter Pistorius 5b52862ed0 fix: update commit status target URL to agent-ci.dev
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 07:27:20 +11:00
Peter Pistorius d273b76c64 fix: show full per-step log in failure summary
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>
2026-03-28 07:15:11 +11:00
Peter Pistorius bdd269ff10 feat: move website to apps/website, improve content and fix SSR
- Move website from website/ to apps/website/ (update pnpm workspace)
- Remove logo icon from navbar, keep AGENT-CI text
- Configure domain routes for agent-ci.dev in wrangler.jsonc
- Replace tabs with stacked tables in compatibility matrix
- Expand all ⚠️ and ❌ notes with reasons and details
- Update compatibility.md to match website content
- Fix useContext SSR error by removing motion/react, using CSS animations
- Add img-src CSP header for pbs.twimg.com (Twitter profile images)
- Remove npx run --all command from hero, rename button to Quick start
- Remove decorative border elements from hero section
- Update footer "Built by" link to rwsdk.com

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 17:27:26 +11:00
Peter Pistorius 60b0105413 Merge pull request #83 from redwoodjs/feat/commit-status
feat: post GitHub commit status via gh CLI
2026-03-26 05:29:29 +13:00
Peter Pistorius 6e7494bc8b fix: remove tickmark from commit status description
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 05:28:33 +13:00
Peter Pistorius 98ccd6fb8f fix: use checkmark instead of emoji in commit status description
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 05:27:13 +13:00
Peter Pistorius ec535e1508 fix: remove 4-byte emoji from commit status description
GitHub's commit status API rejects 4-byte Unicode characters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 03:40:41 +13:00
Peter Pistorius a9cc7f7fca feat: update success commit status message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 03:04:52 +13:00
Peter Pistorius 97227c4251 Merge pull request #77 from simoncrypta/fix/docker-host-ip
fix: make Docker host resolution universal
2026-03-26 02:41:29 +13:00
Simon Gagnon 79c7d65f36 refactor: remove host-side DNS probe for DTU host resolution
Simplify resolveDtuHost to deterministic precedence: AGENT_CI_DTU_HOST > AGENT_CI_DOCKER_BRIDGE_GATEWAY (outside Docker) > host.docker.internal. Removes misleading host-side dns.lookup that did not predict container-side reachability.
2026-03-24 19:31:00 -04:00
Simon Gagnon 1d1e4ec68c fix: preserve external DTU URLs and align host fallback docs
Limit Docker API URL rewrites to loopback hosts, keep alias-based defaults unless an explicit bridge gateway override is configured, and update docs/tests to match behavior.
2026-03-24 19:31:00 -04:00
Simon Gagnon 6d76c2d426 fix: fallback DTU host alias to bridge gateway
Try host.docker.internal first, then fallback to bridge gateway when alias resolution fails, with debug logging for fallback paths.
2026-03-24 19:31:00 -04:00
Simon Gagnon a9878186a0 fix: make Docker host resolution universal 2026-03-24 19:31:00 -04:00
Simon Gagnon c7c35248ec fix: use platform-specific Docker host IP for Linux compatibility
On Linux, host.docker.internal doesn't work by default (only available
since Docker 20.10+ and requires explicit configuration). This change:

- Detects macOS vs Linux and returns appropriate host IP
- Uses 172.17.0.1 (docker bridge gateway) on Linux host
- Uses host.docker.internal on macOS
- Adds containerUrl to ephemeral DTU for container-to-host communication
- Adds ExtraHosts mapping for containers to resolve host.docker.internal

Fixes connectivity issues when running agent-ci on Linux hosts.
2026-03-24 19:31:00 -04:00
Peter Pistorius 6e5375322a feat: post GitHub commit status via gh CLI after run completes
Closes #82

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:59:39 +13:00
github-actions[bot] c9d76f8d2d chore: version packages 2026-03-23 18:35:52 +00:00
Peter Pistorius 99c794a517 fix: use scoped package name @redwoodjs/agent-ci in npx commands
Ensures the commands work even when the package isn't installed as a
local devDependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:32:08 +13:00
Peter Pistorius 6971663e04 fix: address PR review comments on SKILL.md and README
- Add "(in Docker)" clarification to SKILL.md
- Explain --all flag matches on triggers like GitHub
- Add --no-matrix tip for collapsing matrix jobs locally
- Use --quiet flag in CLAUDE.md snippet
- Add "CI was green before you started" instruction for agents

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:18:53 +13:00