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>
* 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>
* 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>
* 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>
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>
* 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>
* 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>
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>
* 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>
* 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>
* 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>
* feat(website): add blog section with first post
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(website): integrate blog section with routing and styles
* Add lock file.
---------
Co-authored-by: Claude Sonnet 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>
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.
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>
- CompatibilityMatrix: keep individual tables rendering, take expanded notes from main
- headers.ts: keep both pbs.twimg.com and unavatar.io in img-src CSP
- wrangler.jsonc: keep agent-ci-website worker name and routes
- compatibility.md: keep expanded notes from feat/website
- Remove welcome.tsx and welcome.module.css (replaced by new site)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Moves the agent-ci.dev marketing site into the monorepo under website/.
Built with RWSDK + Tailwind CSS, deployed to Cloudflare Workers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Explain why concurrency is unsupported (server-side mechanism, no local coordinator)
- Document local actions (uses: ./) as unsupported with fail-fast behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>