Skip to content

Automate nightly compatibility verification on a standalone VM - #25

Merged
TheFehr merged 3 commits into
mainfrom
automate-vm-verification
Jul 31, 2026
Merged

Automate nightly compatibility verification on a standalone VM#25
TheFehr merged 3 commits into
mainfrom
automate-vm-verification

Conversation

@TheFehr

@TheFehr TheFehr commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Closes the loop on verified-versions.json's pending entries: a systemd timer on a standalone VM (no Actions runner involved) can run verify:local --all-pending unattended and push results, instead of a human running it by hand off the issues the Monitor Releases workflow files.
  • --record-failures (verify-local.ts): genuine test failures now get written as a "failed" registry status, distinct from the pre-check "incompatible", so --all-pending stops silently retrying the same broken combo every night.
  • Fixed a temp Foundry data-directory leak in verify-local.ts — the fix(cli): clean up temp data dirs and use shared cache #23 cleanup fix only landed in the public CLI, not this internal script, so every Docker-based run left its full /data dir behind indefinitely.
  • close-resolved-issues.ts: closes/relabels verification-required issues once their entry leaves pending (stable/incompatible → close with a comment; failed → relabel needs-investigation).
  • ops/vm/: wrapper script + systemd unit/timer templates for the VM. Scheduled nightly at 02:00 Europe/Berlin, deliberately without Persistent=true so a missed run waits for the next night instead of a reboot-triggered catch-up landing inside the push blackout window.

Test plan

  • npm run build, npm run lint, npm run format, npm run test all pass
  • Install ops/vm/* on the actual VM and do a live dry run against a real pending entry

Summary by CodeRabbit

  • New Features
    • Added scheduled nightly VM compatibility verification with failure tracking and automated issue reconciliation.
    • Added support for recording failures and system-minor version details.
  • Bug Fixes
    • Improved compatibility checks, latest-build validation, maintenance-window handling, and cleanup resilience.
    • Improved rootless container execution, ownership validation, and command safety.
  • Tests
    • Expanded coverage for container execution, ownership, and cleanup scenarios.
  • Registry Updates
    • Updated the PF2e compatibility entry to incompatible with refreshed details.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f6425532-4176-4ffa-9aec-68f7b913481b

📥 Commits

Reviewing files that changed from the base of the PR and between 3d57825 and 2ad40b5.

📒 Files selected for processing (3)
  • scripts/verify-local.ts
  • src/docker.test.ts
  • src/docker.ts

📝 Walkthrough

Walkthrough

Nightly verification now records failure-aware results, supports rootless Podman, reconciles GitHub issues, and runs through a scheduled non-root systemd service. Compatibility checks use full Foundry versions and system-minor data. Cleanup failures preserve verification results.

Changes

Nightly Compatibility Verification

Layer / File(s) Summary
Compatibility and registry processing
scripts/monitor-releases.ts, scripts/verify-local.ts, scripts/version-utils.ts, verified-versions.json
Compatibility bounds, report validation, system-minor grouping, failure recording, and registry updates now use validated data.
Container identity and cleanup
src/docker.ts, src/docker.test.ts, src/cli/index.ts, scripts/verify-local.ts
Container commands use argument arrays, host UID/GID mapping, rootless Podman support, ownership checks, and guarded cleanup.
GitHub issue reconciliation
scripts/close-resolved-issues.ts, package.json
Resolved registry entries are matched to labeled issues, commented, and closed or relabeled by status.
Nightly orchestration and scheduling
ops/vm/verify-nightly.sh, ops/vm/foundry-verify.service, ops/vm/foundry-verify.timer
A locked nightly workflow runs as foundry-verify, applies disk and blackout checks, pushes results, and reconciles issues.

Sequence Diagram(s)

sequenceDiagram
  participant Timer
  participant Service
  participant NightlyScript
  participant VerifyLocal
  participant IssueReconciler
  participant GitHub
  Timer->>Service: Start nightly service
  Service->>NightlyScript: Execute verification script
  NightlyScript->>VerifyLocal: Verify entries and record results
  NightlyScript->>IssueReconciler: Reconcile resolved entries
  IssueReconciler->>GitHub: Comment and close or relabel issues
Loading

Poem

I’m a rabbit with a nightly tune,
Checking Foundry beneath the moon.
Failed hops are marked with care,
Resolved notes reach GitHub there.
At eight, the timer starts the run.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: automating nightly compatibility verification on a standalone VM.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch automate-vm-verification
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch automate-vm-verification

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ops/vm/foundry-verify.service`:
- Around line 16-18: Update the service dependency declaration by replacing
Requisite=docker.service with Requires=docker.service, while preserving the
existing After and Wants settings so Docker is started before the service runs.
- Around line 8-12: Update the service setup guidance around the foundry-verify
user to remove Docker group membership as an isolation measure. Require rootless
Docker or execution inside a disposable, isolated VM before enabling the
service, while preserving the non-root service-user setup where applicable.

In `@ops/vm/foundry-verify.timer`:
- Around line 7-14: Update verify-nightly.sh to validate the current
Europe/Berlin time immediately before git push, and skip or defer the push when
it falls within the Mon/Tue/Thu/Fri 07:30–16:30 CEST blackout window. Preserve
pushing outside that window and ensure the cutoff is applied after the
verification step.
- Around line 7-9: Update the timer’s OnCalendar directive to include
Europe/Berlin in the calendar expression, and remove the separate TimeZone
directive. Preserve the existing schedule time and RandomizedDelaySec setting.

In `@ops/vm/verify-nightly.sh`:
- Around line 31-34: Update the verification flow around the npm run command to
capture its exit status instead of masking it with `|| true`; preserve that
status while continuing push and reconciliation, then return the original
nonzero status after all cleanup completes.
- Around line 12-15: Update the lock setup around LOCK_FILE and the exec 200
redirection to use a repository-owned lock path or a systemd-managed runtime
directory instead of the predictable world-writable /tmp path, while preserving
the existing file-descriptor locking behavior.
- Around line 41-43: Move the docker system prune cleanup from the main
execution flow into an EXIT trap in verify-nightly.sh so it runs on every script
exit, including failures during pulls, pushes, or issue reconciliation. Preserve
the existing silent, non-failing cleanup behavior and ensure the trap is
registered before those operations can fail.

In `@scripts/close-resolved-issues.ts`:
- Around line 112-115: Update the failed-entry handling in the issue-processing
flow around entry.status === "failed" so failed issues are not selected again:
remove the existing "verification-required" label when adding
"needs-investigation", or skip issues already carrying "needs-investigation".
Preserve the current failure labeling behavior while preventing repeated
processing and comments.

In `@scripts/verify-local.ts`:
- Around line 361-367: Update the cleanup flow around orchestrator.stopAndRemove
so temporary-data removal always runs even when stopAndRemove rejects. Nest the
tmpDataDir cleanup block in a finally associated with the orchestrator cleanup,
while preserving the existing keepContainer guards and removal behavior.
- Around line 113-130: Update the registry validation in the verify-local flow
to throw an error when registry is not an array instead of replacing it with an
empty array. Ensure execution stops before the entry update and fs.writeFileSync
persistence path, preserving the malformed registry contents.
- Around line 338-355: Update the catch-side registry persistence around
upsertRegistryEntry so status "failed" is recorded only when the collected
failures array contains genuine test failures. Route Docker, Playwright,
report-parsing, and metadata errors through a separate incompatible outcome,
preserving them for --all-pending rather than marking them as failed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 065bfe67-aa8c-4918-a545-57ef185a2d70

📥 Commits

Reviewing files that changed from the base of the PR and between 93826a5 and 41eaf68.

📒 Files selected for processing (7)
  • ops/vm/foundry-verify.service
  • ops/vm/foundry-verify.timer
  • ops/vm/verify-nightly.sh
  • package.json
  • scripts/close-resolved-issues.ts
  • scripts/monitor-releases.ts
  • scripts/verify-local.ts

Comment thread ops/vm/foundry-verify.service Outdated
Comment thread ops/vm/foundry-verify.service Outdated
Comment on lines +16 to +18
After=network-online.target docker.service
Wants=network-online.target
Requisite=docker.service

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

systemd-analyze verify ops/vm/foundry-verify.service

Repository: TheFehr/foundry-playwright

Length of output: 217


🏁 Script executed:

sed -n '1,120p' ops/vm/foundry-verify.service
printf '\n---\n'
rg -n "foundry-verify|docker.service|timer|install" -S README.md ops docs . || true

Repository: TheFehr/foundry-playwright

Length of output: 27474


Replace Requisite= with Requires= for Docker. Requisite= only works if docker.service is already active, so timer-triggered runs can fail on a VM where Docker is installed but stopped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/vm/foundry-verify.service` around lines 16 - 18, Update the service
dependency declaration by replacing Requisite=docker.service with
Requires=docker.service, while preserving the existing After and Wants settings
so Docker is started before the service runs.

Comment thread ops/vm/foundry-verify.timer Outdated
Comment thread ops/vm/foundry-verify.timer Outdated
Comment thread ops/vm/verify-nightly.sh Outdated
Comment thread ops/vm/verify-nightly.sh Outdated
Comment thread scripts/close-resolved-issues.ts Outdated
Comment thread scripts/verify-local.ts
Comment thread scripts/verify-local.ts Outdated
Comment thread scripts/verify-local.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/verify-local.ts`:
- Around line 274-291: Update the Docker branch in the verification flow around
reportPath and runPlaywrightInContainer so PLAYWRIGHT_JSON_OUTPUT_NAME uses the
container-visible /work path with the same report filename, while retaining the
host-absolute reportPath for subsequent report parsing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3999871a-7463-4978-8523-f29a93b71f78

📥 Commits

Reviewing files that changed from the base of the PR and between f1b50d5 and f93a929.

📒 Files selected for processing (1)
  • scripts/verify-local.ts

Comment thread scripts/verify-local.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/docker.ts`:
- Around line 82-90: Replace the 0o777 chmod operations in the directory setup
flow with restrictive permissions that still grant container UID/GID 1000:1000
access to dataDir and cacheDir. Use ownership, group permissions, or ACLs as
appropriate, preserving the existing directory creation behavior and ensuring
sensitive Foundry data is not world-writable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb4ef23e-169e-4617-97b1-e06873048b7e

📥 Commits

Reviewing files that changed from the base of the PR and between ab80fc9 and cfa2465.

📒 Files selected for processing (1)
  • src/docker.ts

Comment thread src/docker.ts Outdated
@TheFehr

TheFehr commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/verify-local.ts (1)

152-170: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not pass Podman’s keep-id mode to Docker.

With FOUNDRY_PLAYWRIGHT_ROOTLESS=1, Line 170 always passes --userns=keep-id to docker run. Docker Engine supports only host here; keep-id is Podman-specific, so rootless Docker aborts before tests run. Detect/plumb the selected runtime and add this option only for Podman. (docs.docker.com)

#!/bin/bash
set -euo pipefail

rg -n -C3 'FOUNDRY_PLAYWRIGHT_ROOTLESS|--userns=keep-id|execFileSync\(' \
  scripts/verify-local.ts src/docker.ts ops/vm/verify-nightly.sh

# Run on the target VM to identify the implementation behind `docker`.
docker version --format '{{.Server.Version}}'
docker info --format '{{json .SecurityOptions}}'

Also applies to: 207-229

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 152 - 170, Update the Docker run
construction in the function containing getPlaywrightImageTag and execFileSync
so --userns=keep-id is added only when the selected container runtime is Podman,
never when it is Docker. Detect or plumb the runtime selection into this flow,
while preserving the existing rootless behavior for Podman and omitting the flag
for Docker.
♻️ Duplicate comments (1)
ops/vm/verify-nightly.sh (1)

40-55: 🩺 Stability & Availability | 🟠 Major

Preserve verification failures through the blackout path.

This remains unresolved from the previous review: Line [43] masks every nonzero verification exit, including setup/authentication/runtime failures that never record per-entry results. Additionally, Line [55] exits 0 during blackout, so a captured failure would still be reported as successful. Capture the status and return it after cleanup, including the blackout path.

Proposed fix
+verify_status=0
 npm run verify:local -- --all-pending --docker --update-registry --record-failures --git-commit || true
+verify_status=$?

...
-  exit 0
+  exit "$verify_status"

...
+exit "$verify_status"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/vm/verify-nightly.sh` around lines 40 - 55, Preserve the verification
command’s exit status instead of unconditionally masking it with `|| true`:
capture the status after `npm run verify:local` while allowing cleanup to
continue. Update the blackout branch to perform its existing local cleanup, then
return the captured verification status rather than always exiting successfully;
likewise propagate that status after the normal reconciliation and push flow.
🧹 Nitpick comments (1)
src/docker.test.ts (1)

39-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Also assert the --user mapping.

These tests cover only --userns=keep-id; they do not protect the primary ownership contract introduced by --user ${process.getuid()}:${process.getgid()}. Add an assertion for the expected UID/GID in both configurations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.test.ts` around lines 39 - 54, Update both tests for
DockerFoundryOrchestrator.getRunCommand to assert the command includes the
expected --user UID/GID mapping using process.getuid() and process.getgid(),
while preserving the existing --userns=keep-id assertions for default and
rootless configurations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/monitor-releases.ts`:
- Around line 152-171: Update the incompatibility-note generation to preserve
the complete FVTT version by using the existing `fvtt` value directly instead of
extracting only its major component with `fvtt.split(".")[0]`. Keep the newly
added full-version compatibility comparison in `isCompatibleWithFvtt` unchanged.

In `@src/docker.ts`:
- Around line 93-96: Update copyToContainer() to remove the hard-coded chown -R
1000:1000 step, or replace it with an operation compatible with the configured
runtime identity used by getRunCommand(). Ensure copied files remain usable by
the calling host user without requiring root privileges.

---

Outside diff comments:
In `@scripts/verify-local.ts`:
- Around line 152-170: Update the Docker run construction in the function
containing getPlaywrightImageTag and execFileSync so --userns=keep-id is added
only when the selected container runtime is Podman, never when it is Docker.
Detect or plumb the runtime selection into this flow, while preserving the
existing rootless behavior for Podman and omitting the flag for Docker.

---

Duplicate comments:
In `@ops/vm/verify-nightly.sh`:
- Around line 40-55: Preserve the verification command’s exit status instead of
unconditionally masking it with `|| true`: capture the status after `npm run
verify:local` while allowing cleanup to continue. Update the blackout branch to
perform its existing local cleanup, then return the captured verification status
rather than always exiting successfully; likewise propagate that status after
the normal reconciliation and push flow.

---

Nitpick comments:
In `@src/docker.test.ts`:
- Around line 39-54: Update both tests for
DockerFoundryOrchestrator.getRunCommand to assert the command includes the
expected --user UID/GID mapping using process.getuid() and process.getgid(),
while preserving the existing --userns=keep-id assertions for default and
rootless configurations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5c3522cd-62b3-4376-9144-c13f1ecba28e

📥 Commits

Reviewing files that changed from the base of the PR and between ab80fc9 and aac3a6a.

📒 Files selected for processing (9)
  • ops/vm/foundry-verify.service
  • ops/vm/foundry-verify.timer
  • ops/vm/verify-nightly.sh
  • scripts/close-resolved-issues.ts
  • scripts/monitor-releases.ts
  • scripts/verify-local.ts
  • src/docker.test.ts
  • src/docker.ts
  • verified-versions.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • ops/vm/foundry-verify.timer
  • scripts/close-resolved-issues.ts

Comment thread scripts/monitor-releases.ts
Comment thread src/docker.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/verify-local.ts (1)

281-319: 🗄️ Data Integrity & Integration | 🟠 Major

Do not treat a nonzero Playwright exit as stable when a report exists.

If Playwright exits with an error but leaves a JSON report containing no failed/timedOut specs, execError is ignored and execution proceeds to the stable registry write. Re-throw execError whenever no genuine test failures were extracted.

Proposed fix
     if (fs.existsSync(reportPath)) {
       const report = JSON.parse(fs.readFileSync(reportPath, "utf8"));
       failures = extractFailures(report);
       fs.unlinkSync(reportPath);
     } else if (execError) {
       throw execError;
     }
+
+    if (execError && failures.length === 0) {
+      throw execError;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 281 - 319, The Playwright result
handling must not ignore execError merely because a JSON report exists. Update
the logic following the Docker/non-Docker execution around execError so it
rethrows execError whenever no genuine failed or timedOut specs are extracted,
while preserving report-based failure handling when such specs are present and
only allowing the stable registry write on a successful or genuinely reported
test result.
♻️ Duplicate comments (1)
src/docker.ts (1)

107-110: 🩺 Stability & Availability | 🟠 Major

The copyToContainer() permission issue remains unresolved.

Because the container runs with --user <host uid>:<gid>, docker exec inherits a non-root user. docker cp commonly creates destination files as root, so this chown runs without permission and can leave copied files unusable. Use archive-mode copying and remove the chown, or execute the ownership fix through a supported privileged path.

Also applies to: 199-204

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.ts` around lines 107 - 110, Update copyToContainer() to avoid
relying on chown through docker exec as the non-root container user: use
archive-mode copying so destination files retain the intended ownership, then
remove the ineffective ownership-fix step and any related permission assumptions
in the directory setup flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ops/vm/verify-nightly.sh`:
- Around line 46-47: Update the failure path in the nightly verification wrapper
so recorded failures in verified-versions.json are committed before the
subsequent pull/push. Either change the verify-local handling of --git-commit to
commit failure records as well, or explicitly stage and commit only
verified-versions.json after the verification command fails, preserving the
existing status propagation and push flow.

---

Outside diff comments:
In `@scripts/verify-local.ts`:
- Around line 281-319: The Playwright result handling must not ignore execError
merely because a JSON report exists. Update the logic following the
Docker/non-Docker execution around execError so it rethrows execError whenever
no genuine failed or timedOut specs are extracted, while preserving report-based
failure handling when such specs are present and only allowing the stable
registry write on a successful or genuinely reported test result.

---

Duplicate comments:
In `@src/docker.ts`:
- Around line 107-110: Update copyToContainer() to avoid relying on chown
through docker exec as the non-root container user: use archive-mode copying so
destination files retain the intended ownership, then remove the ineffective
ownership-fix step and any related permission assumptions in the directory setup
flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a064e94-dd9a-4e39-9c6a-729794afeee9

📥 Commits

Reviewing files that changed from the base of the PR and between aac3a6a and 9307a30.

📒 Files selected for processing (5)
  • ops/vm/verify-nightly.sh
  • scripts/monitor-releases.ts
  • scripts/verify-local.ts
  • src/docker.test.ts
  • src/docker.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/monitor-releases.ts

Comment thread ops/vm/verify-nightly.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/docker.ts (1)

107-110: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Don’t assume cp -a preserves usable ownership here. The existing-directory path still skips any ownership fixup, and docker cp -a behaves differently across runtimes: Docker preserves source UID/GID, while Podman maps ownership to the container’s primary UID/GID. That can leave copied files unwritable by the non-root process; add an explicit ownership/access check for existing dirs and copied inputs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.ts` around lines 107 - 110, Update the directory setup flow near
getRunCommand() so it does not assume bind-mount ownership or cp -a preservation
is usable. For existing directories and copied inputs, explicitly verify
ownership and write access for the non-root runtime user, applying the required
ownership fixup when the check fails while preserving the current behavior for
correctly accessible paths.
scripts/verify-local.ts (3)

281-339: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require a fresh, structurally valid report before recording the outcome.

The new guard only handles stale reports with zero extracted failures. If a prior report contains failures and the current process crashes before writing a new report, those stale failures reach Line 435 and permanently record status: "failed", preventing --all-pending retries. Conversely, a zero-exit run with no report—or a JSON object without the expected suites array—is accepted as success.

Remove the report before starting, use a unique per-run path, and require a valid report before extracting failures.

Proposed fix
 const reportPath = path.join(process.cwd(), `.playwright-report-${version}.json`);
+fs.rmSync(reportPath, { force: true });

...
-    if (fs.existsSync(reportPath)) {
-      const report = JSON.parse(fs.readFileSync(reportPath, "utf8"));
+    if (!fs.existsSync(reportPath)) {
+      throw execError ?? new Error("Playwright produced no JSON report");
+    }
+    const report = JSON.parse(fs.readFileSync(reportPath, "utf8"));
+    if (!report || !Array.isArray(report.suites)) {
+      throw new Error("Playwright produced an invalid JSON report");
+    }

Also applies to: 435-460

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 281 - 339, Update the test execution
flow around reportPath, execError, and extractFailures to remove any existing
report before starting and use a unique per-run report path. After execution,
only parse the report when it exists and has the expected structural suites
array; otherwise propagate execError or treat the run as unsuccessful rather
than recording success. Ensure stale or malformed reports cannot populate
failures or allow a failed/crashed run to be recorded as status "failed" before
--all-pending retries.

341-348: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Validate .foundry_metadata.json before using it.

JSON.parse is assigned directly to meta; a valid partial file such as {} causes later dereferences of meta.system.version or meta.modules.filter to throw. The latter occurs inside the failure handler and can mask the original test failure, preventing the intended result or registry update. Since the file is deleted only after execution, interrupted runs can also leave stale metadata.

Parse into unknown, validate the required shape, clear stale metadata before execution, and treat invalid metadata as infrastructure failure rather than dereferencing it.

Also applies to: 383-390, 440-460

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 341 - 348, Harden the metadata handling
around metaPath and the verify-local execution flow: remove any stale
.foundry_metadata.json before tests run, parse newly written contents as
unknown, and validate the required system.version and modules shape before
assigning to meta. Treat missing, malformed, or invalid metadata as an
infrastructure failure while preserving the original test failure and preventing
dereferences in the failure handler and registry-update path.

312-317: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid shell interpolation in the Playwright call

playwrightArgs is copied from process.argv and concatenated into the execSync string, so a quoted flag value can inject shell metacharacters into the inner shell. Switch to execFileSync("npx", ["playwright", "test", ...testFiles, "--workers=1", "--reporter=line,json", ...playwrightArgs], ...) instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 312 - 317, Replace the string-based
execSync Playwright invocation with execFileSync, passing "npx" and the
Playwright command arguments as separate array elements in the existing order,
including testFiles and playwrightArgs. Preserve the current stdio and
environment options while avoiding shell interpolation.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/docker.ts`:
- Around line 193-202: Update copyToContainer() to replace interpolated execSync
Docker commands with execFileSync("docker", ...) argument arrays, including the
mkdir and docker cp operations. Pass containerName, localPath, and containerPath
as separate arguments so shell metacharacters are not interpreted, while
preserving the existing stdio and Docker options.

---

Outside diff comments:
In `@scripts/verify-local.ts`:
- Around line 281-339: Update the test execution flow around reportPath,
execError, and extractFailures to remove any existing report before starting and
use a unique per-run report path. After execution, only parse the report when it
exists and has the expected structural suites array; otherwise propagate
execError or treat the run as unsuccessful rather than recording success. Ensure
stale or malformed reports cannot populate failures or allow a failed/crashed
run to be recorded as status "failed" before --all-pending retries.
- Around line 341-348: Harden the metadata handling around metaPath and the
verify-local execution flow: remove any stale .foundry_metadata.json before
tests run, parse newly written contents as unknown, and validate the required
system.version and modules shape before assigning to meta. Treat missing,
malformed, or invalid metadata as an infrastructure failure while preserving the
original test failure and preventing dereferences in the failure handler and
registry-update path.
- Around line 312-317: Replace the string-based execSync Playwright invocation
with execFileSync, passing "npx" and the Playwright command arguments as
separate array elements in the existing order, including testFiles and
playwrightArgs. Preserve the current stdio and environment options while
avoiding shell interpolation.

In `@src/docker.ts`:
- Around line 107-110: Update the directory setup flow near getRunCommand() so
it does not assume bind-mount ownership or cp -a preservation is usable. For
existing directories and copied inputs, explicitly verify ownership and write
access for the non-root runtime user, applying the required ownership fixup when
the check fails while preserving the current behavior for correctly accessible
paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 87a1442a-4f66-44bb-b6a8-7616d1c7efd5

📥 Commits

Reviewing files that changed from the base of the PR and between 9307a30 and 76952ab.

📒 Files selected for processing (2)
  • scripts/verify-local.ts
  • src/docker.ts

Comment thread src/docker.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/docker.ts (1)

156-176: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove shell construction from the remaining Docker operations.

getRunCommand() interpolates containerName, envPath, mount paths, and version into a string executed by execSync at Line 144. Quotes do not protect embedded quotes or command substitutions, so a configuration-derived value can execute shell syntax. Migrate this and the other execSync Docker calls in this class to execFileSync("docker", [...args]); fixing only copyToContainer() leaves the command-injection path open.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.ts` around lines 156 - 176, Replace shell-string construction in
getRunCommand and every other Docker operation in the class with
execFileSync("docker", [...args]) using separate argument values for
containerName, envPath, mount paths, version, and flags. Remove shell quoting
and command joining while preserving the existing Docker arguments and runtime
behavior across all operations.

Source: Linters/SAST tools

scripts/verify-local.ts (1)

342-361: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail when the required JSON report is absent.

If Playwright exits successfully but does not create the requested report, this path falls through as a pass and can persist a stable entry using default/unknown metadata. Treat a missing report as an infrastructure error regardless of exit status.

Proposed fix
-    } else if (execError) {
-      // Playwright failed to start or crashed without producing a report.
-      throw execError;
+    } else {
+      throw (
+        execError ??
+        new Error(`Playwright completed without producing JSON report: ${reportPath}`)
+      );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 342 - 361, The report-handling branch
around isPlaywrightReport must treat a missing reportPath as an infrastructure
failure even when execError is unset. Update the else branch to throw an
appropriate missing-report error, while preserving execError propagation when
Playwright fails without producing a report.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/verify-local.ts`:
- Around line 368-376: Update the metadata parsing around isCapturedMetadata to
catch JSON.parse failures and fall back to the existing unknown metadata
behavior. Strengthen isCapturedMetadata to require a string system.id and ensure
every module has string id and version fields before assigning rawMeta to meta.
Preserve cleanup and malformed-metadata handling without allowing invalid values
into the summary or registry.

In `@src/docker.ts`:
- Around line 107-118: Update ensureWritableDir to validate the directory
itself, not only its children: include dir in ownership repair and verify it has
W_OK and X_OK access for the current process. Ensure this validation runs for
both dataDir and cacheDir before container startup, while preserving the
existing best-effort repair behavior.
- Around line 240-246: Update copyToContainer so ownership handling is
runtime-specific instead of relying on docker cp -a: preserve the existing
Podman behavior while ensuring Docker-copied files are owned by the container
user and remain writable. Use the configured runtime information and existing
container identity symbols to normalize ownership before or during the copy, and
add coverage for both Docker and Podman ownership behavior.

---

Outside diff comments:
In `@scripts/verify-local.ts`:
- Around line 342-361: The report-handling branch around isPlaywrightReport must
treat a missing reportPath as an infrastructure failure even when execError is
unset. Update the else branch to throw an appropriate missing-report error,
while preserving execError propagation when Playwright fails without producing a
report.

In `@src/docker.ts`:
- Around line 156-176: Replace shell-string construction in getRunCommand and
every other Docker operation in the class with execFileSync("docker", [...args])
using separate argument values for containerName, envPath, mount paths, version,
and flags. Remove shell quoting and command joining while preserving the
existing Docker arguments and runtime behavior across all operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bac8ff35-7bfe-4aba-81d7-9dbb0636a47b

📥 Commits

Reviewing files that changed from the base of the PR and between 79f3e56 and e14ecd6.

📒 Files selected for processing (2)
  • scripts/verify-local.ts
  • src/docker.ts

Comment thread scripts/verify-local.ts Outdated
Comment thread src/docker.ts
Comment thread src/docker.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/docker.ts (1)

198-235: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unhandled exception if the directory itself isn't readable.

fixOwnership(dir) gracefully catches a failed chownSync into unfixable, but the subsequent fs.readdirSync(dir) at Line 220 has no such guard. If dir is owned by a different uid with no group/other read+execute bits — exactly the "switching automation users" scenario this function's own comments call out as having happened in practice — readdirSync throws an uncaught, raw fs error instead of the clear, actionable diagnostic built at Lines 230-234.

🐛 Proposed fix
     fixOwnership(dir);
-    for (const entry of fs.readdirSync(dir)) {
-      fixOwnership(path.join(dir, entry));
-    }
+    try {
+      for (const entry of fs.readdirSync(dir)) {
+        fixOwnership(path.join(dir, entry));
+      }
+    } catch {
+      unfixable.add(dir);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.ts` around lines 198 - 235, Update ensureWritableDir, specifically
the directory-entry iteration after fixOwnership(dir), to catch readdirSync
failures and record dir in unfixable instead of allowing a raw filesystem
exception to escape. Preserve the existing actionable error construction and
continue checking accessibility so the final diagnostic identifies the
inaccessible directory.
🧹 Nitpick comments (1)
scripts/verify-local.ts (1)

342-369: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Malformed-report handling is asymmetric with the metadata parsing added below.

JSON.parse(fs.readFileSync(reportPath, ...)) at Line 343 isn't wrapped in try/catch, unlike the .foundry_metadata.json parsing added at Lines 376-389. A truncated/corrupt report JSON throws uncaught here, skips fs.unlinkSync(reportPath) (leaking the report file), and falls through to the outer catch — which is still safe (failures stays empty, so it won't be misrecorded as "failed"), but the error message is a raw SyntaxError instead of the clear "malformed report" diagnostic this code is designed to produce.

♻️ Proposed hardening
 if (fs.existsSync(reportPath)) {
-  const rawReport: unknown = JSON.parse(fs.readFileSync(reportPath, "utf8"));
-  fs.unlinkSync(reportPath);
-  const validReport = isPlaywrightReport(rawReport);
-  if (validReport) {
-    failures = extractFailures(rawReport);
-  }
+  const rawContent = fs.readFileSync(reportPath, "utf8");
+  fs.unlinkSync(reportPath);
+  let validReport = false;
+  try {
+    const rawReport: unknown = JSON.parse(rawContent);
+    validReport = isPlaywrightReport(rawReport);
+    if (validReport) {
+      failures = extractFailures(rawReport);
+    }
+  } catch {
+    validReport = false;
+  }
   if (!validReport || (failures.length === 0 && execError)) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 342 - 369, Wrap the report JSON parsing
in the report-handling block around JSON.parse, ensuring reportPath is removed
even when parsing fails. Convert parse failures into the existing
malformed-report diagnostic, while preserving execError precedence and the
current valid-report failure extraction flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/docker.ts`:
- Around line 198-235: Update ensureWritableDir, specifically the
directory-entry iteration after fixOwnership(dir), to catch readdirSync failures
and record dir in unfixable instead of allowing a raw filesystem exception to
escape. Preserve the existing actionable error construction and continue
checking accessibility so the final diagnostic identifies the inaccessible
directory.

---

Nitpick comments:
In `@scripts/verify-local.ts`:
- Around line 342-369: Wrap the report JSON parsing in the report-handling block
around JSON.parse, ensuring reportPath is removed even when parsing fails.
Convert parse failures into the existing malformed-report diagnostic, while
preserving execError precedence and the current valid-report failure extraction
flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb5da8cd-d936-46fe-a58d-071085def53c

📥 Commits

Reviewing files that changed from the base of the PR and between e14ecd6 and 3acad08.

📒 Files selected for processing (3)
  • scripts/verify-local.ts
  • src/docker.test.ts
  • src/docker.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/docker.test.ts

@TheFehr

TheFehr commented Jul 29, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/docker.ts`:
- Around line 250-255: Update stopAndRemove to stop swallowing cleanup errors:
ignore only the expected “container not found” case, while propagating daemon,
permission, and other Docker failures so callers retain the temporary bind mount
when removal is unsuccessful. Preserve the existing stop-then-remove sequence
and use the execFileSync error details to distinguish the not-found condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bd5d9890-ef7d-4ca5-80c1-10ec5c89d4eb

📥 Commits

Reviewing files that changed from the base of the PR and between e14ecd6 and bc26f5f.

📒 Files selected for processing (3)
  • scripts/verify-local.ts
  • src/docker.test.ts
  • src/docker.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/docker.test.ts

Comment thread src/docker.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/verify-local.ts (1)

469-478: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve the pending registry key when metadata is unavailable.

A passing run with missing/malformed metadata writes systemMinor: "unknown" instead of the target’s systemMinor. The original pending entry remains unresolved and will be verified repeatedly.

Proposed fix
 const realModules = meta.modules.filter((m) => m.id !== "fake-module");
+const registrySystemVersion =
+  installedSystemVersion !== "unknown"
+    ? installedSystemVersion
+    : (systemVersion ?? "unknown");
+const registrySystemMinor =
+  installedSystemVersion !== "unknown"
+    ? minorOf(installedSystemVersion)
+    : (systemMinor ?? minorOf(registrySystemVersion));

 upsertRegistryEntry({
   fvtt: version,
   system: meta.system.id,
-  systemMinor: minorOf(installedSystemVersion),
-  systemVersion: installedSystemVersion,
+  systemMinor: registrySystemMinor,
+  systemVersion: registrySystemVersion,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 469 - 478, Update the
upsertRegistryEntry call in the local verification flow to preserve the pending
registry entry’s target systemMinor when metadata is missing or malformed,
rather than deriving or writing "unknown". Reuse the target/pending entry’s
systemMinor while keeping the existing metadata-derived value for valid metadata
and leaving the remaining registry fields unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/verify-local.ts`:
- Around line 521-539: Handle failures from temporary data-directory removal in
the cleanup block around tmpDataDir and fs.rmSync so they cannot override the
verification result or abort an --all-pending batch. Track the removal failure
similarly to cleanupFailed, catch and log the error with the directory path, and
preserve the existing behavior of skipping removal when cleanupFailed or
keepContainer is set.

---

Outside diff comments:
In `@scripts/verify-local.ts`:
- Around line 469-478: Update the upsertRegistryEntry call in the local
verification flow to preserve the pending registry entry’s target systemMinor
when metadata is missing or malformed, rather than deriving or writing
"unknown". Reuse the target/pending entry’s systemMinor while keeping the
existing metadata-derived value for valid metadata and leaving the remaining
registry fields unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8e54a52-36e1-4581-9bd4-df7e68e0690e

📥 Commits

Reviewing files that changed from the base of the PR and between bc26f5f and f515eee.

📒 Files selected for processing (3)
  • scripts/verify-local.ts
  • src/cli/index.ts
  • src/docker.ts

Comment thread scripts/verify-local.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/verify-local.ts (1)

474-487: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Resolve captured metadata before writing the stable entry.

isCapturedMetadata accepts any string version, but minorOf returns "unknown" for incomplete or invalid values. The check at Lines [474]-[477] recognizes only the literal "unknown", so a value such as "garbage" can write systemMinor: "unknown" and orphan the pending entry. When metadata is missing, Lines [483] and [487] also persist systemVersion and notes as "unknown" instead of the target version.

Compute one validated resolvedSystemVersion, then derive both the registry key and persisted fields from it.

Proposed fix
+const installedMinor = minorOf(installedSystemVersion);
+const hasValidInstalledVersion =
+  installedSystemVersion !== "unknown" && installedMinor !== "unknown";
+const resolvedSystemVersion = hasValidInstalledVersion
+  ? installedSystemVersion
+  : (systemVersion ?? "unknown");
 const resolvedSystemMinor =
-  installedSystemVersion !== "unknown"
-    ? minorOf(installedSystemVersion)
-    : (systemMinor ?? "unknown");
+  hasValidInstalledVersion
+    ? installedMinor
+    : (systemMinor ?? minorOf(resolvedSystemVersion));

...
-        systemVersion: installedSystemVersion,
+        systemVersion: resolvedSystemVersion,
...
-        notes: `Verified locally with ${meta.system.id} v${installedSystemVersion}.`,
+        notes: `Verified locally with ${meta.system.id} v${resolvedSystemVersion}.`,

Also applies to: 603-610

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 474 - 487, Update the stable-entry
construction around resolvedSystemMinor and the corresponding flow near the
alternate occurrence so one validated resolvedSystemVersion is computed first:
use the captured version only when it passes metadata validation, otherwise fall
back to the target version. Derive resolvedSystemMinor from that value, use it
for the registry key, and persist it in systemVersion and the verification notes
instead of allowing invalid or unknown captured metadata to orphan the pending
entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@scripts/verify-local.ts`:
- Around line 474-487: Update the stable-entry construction around
resolvedSystemMinor and the corresponding flow near the alternate occurrence so
one validated resolvedSystemVersion is computed first: use the captured version
only when it passes metadata validation, otherwise fall back to the target
version. Derive resolvedSystemMinor from that value, use it for the registry
key, and persist it in systemVersion and the verification notes instead of
allowing invalid or unknown captured metadata to orphan the pending entry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24ccac8d-6e32-4e67-8c34-e80117b1f680

📥 Commits

Reviewing files that changed from the base of the PR and between f515eee and 22d433d.

📒 Files selected for processing (2)
  • scripts/verify-local.ts
  • src/cli/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/verify-local.ts`:
- Around line 475-492: In scripts/verify-local.ts lines 475-492, only fall back
to the requested system version when buildManifestUrl() supplied a manifest URL;
otherwise keep resolvedSystemVersion and resolvedSystemMinor as "unknown" rather
than creating a stable entry for an unpinned latest-version run. In
scripts/verify-local.ts lines 513-515, derive systemMinor from the resolved
captured version, and leave the entry pending instead of marking it failed when
neither a captured version nor an explicit pin establishes the system version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4050d02-975e-464c-a831-0c15f29a9ac7

📥 Commits

Reviewing files that changed from the base of the PR and between 22d433d and e78b17c.

📒 Files selected for processing (1)
  • scripts/verify-local.ts

Comment thread scripts/verify-local.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
ops/vm/foundry-verify.service (2)

31-34: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Make Git push authentication explicit.

gh ssh-key add --type signing registers a signing key. It does not configure the Git remote used by git push. Document gh auth setup-git after HTTPS authentication, or document a separate SSH authentication key. Then run git push --dry-run as foundry-verify. GitHub CLI documents these as separate operations. (cli.github.com)

Proposed documentation addition
 # `gh ssh-key add ~/.ssh/foundry-verify-signing.pub --type signing --title "foundry-verify"`.
+# For an HTTPS Git remote, also run:
+#   gh auth setup-git
+#   git push --dry-run
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/vm/foundry-verify.service` around lines 31 - 34, Update the setup
instructions around the `gh ssh-key add --type signing` command to explicitly
configure Git push authentication, preferably by documenting `gh auth setup-git`
after HTTPS authentication. Also instruct users to run `git push --dry-run` as
`foundry-verify`, while keeping the signing-key registration steps separate.

30-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a preflight test for unattended SSH signing.

The nightly path creates signed commits with --git-commit. Add a temporary-repository test that runs as foundry-verify, after applying the signing configuration, and fails if git commit --allow-empty or git verify-commit HEAD fails. Ensure ssh-keygen is installed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ops/vm/foundry-verify.service` at line 30, Add a preflight test in the
foundry-verify service after applying the SSH signing configuration: verify
ssh-keygen is installed, create a temporary repository with the required signing
setup, run as foundry-verify, and fail the service if either git commit
--allow-empty or git verify-commit HEAD fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ops/vm/foundry-verify.service`:
- Line 28: Update the git configuration setup comment near the existing
user.email command to also configure user.name with a stable bot identity,
ensuring both values are set before git commit runs for a fresh foundry-verify
account.
- Line 24: Create the ~/.ssh directory with mode 700 before the ssh-keygen
command in the signing-key setup instructions. Add the directory creation step
immediately before the command so key generation works when the parent directory
is absent.

---

Nitpick comments:
In `@ops/vm/foundry-verify.service`:
- Around line 31-34: Update the setup instructions around the `gh ssh-key add
--type signing` command to explicitly configure Git push authentication,
preferably by documenting `gh auth setup-git` after HTTPS authentication. Also
instruct users to run `git push --dry-run` as `foundry-verify`, while keeping
the signing-key registration steps separate.
- Line 30: Add a preflight test in the foundry-verify service after applying the
SSH signing configuration: verify ssh-keygen is installed, create a temporary
repository with the required signing setup, run as foundry-verify, and fail the
service if either git commit --allow-empty or git verify-commit HEAD fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2ffd8cf1-b921-41d6-b473-f2bd21a82a33

📥 Commits

Reviewing files that changed from the base of the PR and between e78b17c and 166324f.

📒 Files selected for processing (2)
  • ops/vm/foundry-verify.service
  • scripts/verify-local.ts

Comment thread ops/vm/foundry-verify.service
Comment thread ops/vm/foundry-verify.service
Replaces manual verify:local runs with a standalone VM (rootless Podman,
no docker-group/root-equivalent access, since it executes third-party
FoundryVTT/system/module content on every run) that consumes "pending"
entries the cloud Monitor Releases workflow files as GitHub issues, runs
the Docker-based verification suite nightly via a systemd timer, and
pushes results back - closing the loop by reconciling verified-versions.json
against open issues (scripts/close-resolved-issues.ts).

Key pieces:
- ops/vm/{foundry-verify.service,foundry-verify.timer,verify-nightly.sh}:
  the systemd unit/timer/wrapper script, scheduled well clear of both this
  host's backup-job CPU peaks and the Mon/Tue/Thu/Fri 07:30-16:30 CEST push
  blackout, with disk-usage guards, file locking, and a same-day blackout
  re-check immediately before push as defense in depth.
- scripts/verify-local.ts: containerizes the Playwright test client itself
  (not just the Foundry server), adds --record-failures/--git-commit for
  unattended runs, and - after several rounds of hardening - only ever
  trusts a system version for the registry from validated captured
  metadata or an actually-used manifest pin, never a bare requested
  version that might not reflect what was actually installed.
- scripts/close-resolved-issues.ts (new): reconciles resolved registry
  entries against open "verification-required" issues.
- scripts/monitor-releases.ts: fixed to compare full version strings
  (not just major integers) and to keep checking newer builds within an
  already-stable Foundry generation, instead of freezing on the first
  build that went stable.
- src/docker.ts: DockerFoundryOrchestrator runs containers with --user
  matching the calling process (fixing a uid:gid bind-mount mismatch) and
  optionally --userns=keep-id under a real Podman runtime (detected, not
  assumed) for rootless support; all Docker invocations use execFileSync
  array args rather than shell-interpolated strings.
- foundry-verify's commits are SSH-signed (dedicated passphrase-less key,
  used only for signing, never repo auth).

Built and iteratively hardened via live dry runs directly on the
provisioned VM (192.168.6.92) and multiple automated code-review passes,
not just local testing.
@TheFehr
TheFehr force-pushed the automate-vm-verification branch from 166324f to 5258bd3 Compare July 31, 2026 18:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
scripts/verify-local.ts (4)

623-631: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Validate foundry in isCapturedMetadata.

The predicate returns value is CapturedMetadata, but it never checks foundry. CapturedMetadata declares foundry: string. After line 388 assigns the parsed value, TypeScript treats meta.foundry as a string even when the file omits it or supplies a number. No current reader uses meta.foundry, so this is latent. Add the check so the predicate matches the declared type.

Proposed fix
 function isCapturedMetadata(value: unknown): value is CapturedMetadata {
   if (typeof value !== "object" || value === null) return false;
   const v = value as Record<string, unknown>;
+  if (typeof v["foundry"] !== "string") return false;
   const sys = v["system"];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 623 - 631, Update isCapturedMetadata to
validate that the top-level foundry property is a string before returning true,
alongside the existing system and modules checks, so the CapturedMetadata type
guard matches its declared shape.

192-202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the positional parameter list with an options object.

verifyVersion now takes nine positional parameters. Four consecutive booleans (isDocker, updateRegistry, recordFailures, keepContainer) and two consecutive string | undefined parameters follow each other. A swapped argument at the call site compiles without error and silently changes behavior. An options object makes each value explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 192 - 202, Update verifyVersion to
accept a single options object instead of its nine positional parameters, using
named properties for system, modules, systemVersion, systemMinor, isDocker,
updateRegistry, recordFailures, and keepContainer. Update every call site to
pass the corresponding named values and adjust the function body to read from
the options object.

35-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share one minorOf implementation.

scripts/monitor-releases.ts contains an identical minorOf. Both scripts define the minor-grouping rule that the registry key depends on. If one copy changes, registry keys silently diverge. Move the function into a shared module and import it in both scripts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 35 - 38, Extract the duplicated minorOf
implementation from scripts/verify-local.ts and scripts/monitor-releases.ts into
a shared module, then import and use that shared function in both scripts.
Preserve the existing major.minor or "unknown" behavior so registry keys
continue using one consistent grouping rule.

507-542: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared system-version resolution.

Lines 517-523 repeat the logic of lines 476-481, and line 511 repeats the fake-module filter of line 466. Both paths must agree on what counts as a trustworthy system version. If one copy changes, the success path and the failure path key entries differently. Extract one helper that takes meta, system, and systemVersion and returns the resolved version, then call it from both paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify-local.ts` around lines 507 - 542, Extract the duplicated
system-version resolution into a shared helper accepting meta, system, and
systemVersion, then use it in both the success and failure paths so they apply
identical trust rules. Also centralize the realModules filtering that excludes
“fake-module” and reuse it from both paths, preserving the existing
unresolved-version behavior.
src/docker.ts (1)

30-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deduplicating isPodmanRuntime().

scripts/verify-local.ts defines an identical isPodmanRuntime() (same implementation, same doc-comment intent) independently of this exported function. Two copies of Podman-detection logic can silently drift if one is updated without the other, e.g., a broadened detection regex fixed in only one place.

Export this function as the single source of truth and import it from scripts/verify-local.ts instead of duplicating the implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.ts` around lines 30 - 42, Use the exported isPodmanRuntime() from
src/docker.ts as the single Podman-detection implementation. Remove the
duplicate function and related detection logic from scripts/verify-local.ts,
import the shared symbol there, and preserve its existing callers and behavior.
src/docker.test.ts (1)

48-84: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add coverage for the other newly hardened DockerFoundryOrchestrator behavior.

These three tests cover getRunCommand()'s rootless/Podman branch well. ensureWritableDir(), stopAndRemove(), and copyToContainer() in src/docker.ts were also substantially reworked in this PR (ownership repair, no such container detection, archive-copy ownership verification) after multiple past reviews found critical bugs in exactly this logic (silently swallowed cleanup errors, world-writable directories, shell-injected copy commands). None of that logic has test coverage in this file.

Add tests that mock fs (for ensureWritableDir's stat/chown/access paths) and execFileSync (for stopAndRemove's "no such container" vs. real-failure branching, and copyToContainer's ownership-mismatch error) to lock in the fixed behavior against regression.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.test.ts` around lines 48 - 84, Extend src/docker.test.ts with
focused coverage for DockerFoundryOrchestrator.ensureWritableDir, stopAndRemove,
and copyToContainer. Mock fs to exercise ensureWritableDir stat, chown, and
access behavior; mock execFileSync to verify stopAndRemove ignores only “no such
container” failures while propagating real failures, and copyToContainer rejects
ownership mismatches. Preserve the existing rootless getRunCommand tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/close-resolved-issues.ts`:
- Around line 95-129: Wrap the processing body for each entry in the resolved
loop with a try/catch so failures from githubRequest calls do not abort
subsequent entries. Log the affected issue or entry and the caught error in the
catch block, then continue iterating through the remaining entries while
preserving the existing success and status-specific behavior.
- Around line 89-93: Update the openIssues retrieval around the githubRequest
call to paginate the verification-required open-issue query, requesting
successive pages with the existing per_page limit until a response contains
fewer than 100 issues. Accumulate every page into one combined list, then use
that complete list for the existing search and close flow.

In `@src/docker.ts`:
- Around line 198-245: Update the fixOwnership helper inside ensureWritableDir
to include fs.statSync(entryPath) within its existing try/catch. When stat or
ownership correction fails, add entryPath to unfixable so dangling or removed
entries produce the function’s intended diagnostic instead of escaping as a raw
filesystem error.

---

Nitpick comments:
In `@scripts/verify-local.ts`:
- Around line 623-631: Update isCapturedMetadata to validate that the top-level
foundry property is a string before returning true, alongside the existing
system and modules checks, so the CapturedMetadata type guard matches its
declared shape.
- Around line 192-202: Update verifyVersion to accept a single options object
instead of its nine positional parameters, using named properties for system,
modules, systemVersion, systemMinor, isDocker, updateRegistry, recordFailures,
and keepContainer. Update every call site to pass the corresponding named values
and adjust the function body to read from the options object.
- Around line 35-38: Extract the duplicated minorOf implementation from
scripts/verify-local.ts and scripts/monitor-releases.ts into a shared module,
then import and use that shared function in both scripts. Preserve the existing
major.minor or "unknown" behavior so registry keys continue using one consistent
grouping rule.
- Around line 507-542: Extract the duplicated system-version resolution into a
shared helper accepting meta, system, and systemVersion, then use it in both the
success and failure paths so they apply identical trust rules. Also centralize
the realModules filtering that excludes “fake-module” and reuse it from both
paths, preserving the existing unresolved-version behavior.

In `@src/docker.test.ts`:
- Around line 48-84: Extend src/docker.test.ts with focused coverage for
DockerFoundryOrchestrator.ensureWritableDir, stopAndRemove, and copyToContainer.
Mock fs to exercise ensureWritableDir stat, chown, and access behavior; mock
execFileSync to verify stopAndRemove ignores only “no such container” failures
while propagating real failures, and copyToContainer rejects ownership
mismatches. Preserve the existing rootless getRunCommand tests.

In `@src/docker.ts`:
- Around line 30-42: Use the exported isPodmanRuntime() from src/docker.ts as
the single Podman-detection implementation. Remove the duplicate function and
related detection logic from scripts/verify-local.ts, import the shared symbol
there, and preserve its existing callers and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3dab3707-f66e-4569-aca6-acb1bb1aca1f

📥 Commits

Reviewing files that changed from the base of the PR and between 166324f and 5258bd3.

📒 Files selected for processing (11)
  • ops/vm/foundry-verify.service
  • ops/vm/foundry-verify.timer
  • ops/vm/verify-nightly.sh
  • package.json
  • scripts/close-resolved-issues.ts
  • scripts/monitor-releases.ts
  • scripts/verify-local.ts
  • src/cli/index.ts
  • src/docker.test.ts
  • src/docker.ts
  • verified-versions.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • ops/vm/foundry-verify.timer
  • package.json
  • ops/vm/foundry-verify.service
  • verified-versions.json
  • ops/vm/verify-nightly.sh
  • scripts/monitor-releases.ts
  • src/cli/index.ts

Comment thread scripts/close-resolved-issues.ts Outdated
Comment thread scripts/close-resolved-issues.ts
Comment thread src/docker.ts
- scripts/close-resolved-issues.ts: per-entry try/catch so one issue's API
  failure (deleted issue, rate limit, transient network error) doesn't
  abort reconciliation for every other independent entry; paginate the
  open-issues query instead of silently missing anything past the first
  100 open verification-required issues.
- src/docker.ts: fixOwnership()'s fs.statSync call is now inside its own
  try/catch, so a stat failure (dangling entry, race with a concurrent
  process) folds into ensureWritableDir's own actionable diagnostic
  instead of escaping as a raw exception.
- scripts/verify-local.ts: isCapturedMetadata now validates the declared
  foundry: string field too, matching its own CapturedMetadata interface
  (no behavioral impact - meta.foundry is never read - but the guard
  should actually validate what it claims to).
- scripts/verify-local.ts: verifyVersion() takes a single options object
  instead of 9 positional parameters, removing a real footgun for future
  edits (same-typed positional args aren't distinguished by the compiler).
- Deduplicated minorOf into a new scripts/version-utils.ts, shared by
  verify-local.ts and monitor-releases.ts - the two copies had actually
  drifted: monitor-releases.ts's version lacked the "unknown" fallback
  entirely, producing "X.undefined" instead of "unknown" for a malformed
  version string.
- scripts/verify-local.ts: extracted the system-version resolution logic
  (the manifestUrl-gated fallback) and fake-module filtering into shared
  helpers (resolveVerifiedSystemVersion, filterRealModules) used by both
  the success and failure registry-write paths - this exact duplication
  is why an earlier fix to this logic had to be applied twice and could
  have drifted apart again on a future edit to only one copy.
- src/docker.test.ts: added focused coverage for ensureWritableDir (real
  temp filesystem - creation, already-correctly-owned, and a chmod-000
  directory that can't be fixed), stopAndRemove (Docker's and Podman's
  differing "no such container" phrasings tolerated, a real
  daemon-unreachable failure propagated), and copyToContainer (matching
  vs. mismatched post-copy ownership) - these three methods had zero
  coverage despite being the subject of most of this review thread's
  actual bugs.

Skipped: a claimed duplicate Podman-detection implementation in
verify-local.ts doesn't exist - it already imports and reuses the shared
isPodmanRuntime from src/docker.ts.
@TheFehr

TheFehr commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/docker.ts (2)

293-302: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Correct the comment: only docker exec is used.

The comment says "via an ephemeral container or exec (if running)", but the code only runs docker exec. The call therefore requires a running container and fails otherwise. Update the comment to describe the actual behavior.

Proposed change
-    // Ensure destination directory exists via an ephemeral container or exec (if running)
+    // Ensure the destination directory exists. This requires the container to
+    // be running, since it uses `docker exec`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.ts` around lines 293 - 302, Update the comment immediately above
the docker exec call to state that the destination directory is created using
docker exec in the running container. Remove the inaccurate reference to an
ephemeral container or conditional execution, and note that this operation
requires the configured container to be running.

120-137: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a timeout to the image pull calls.

execFileSync waits without limit by default. If a registry stalls, docker pull blocks the nightly run for an unbounded time. Pass an explicit timeout so a stalled pull fails fast. The missing-image pull must still fail the run, and the update pull already tolerates failures.

Proposed change
     if (!imageExists) {
       console.log(`[DockerOrchestrator] Image ${image} not found locally. Pulling...`);
-      execFileSync("docker", ["pull", image], { stdio: "inherit" });
+      execFileSync("docker", ["pull", image], { stdio: "inherit", timeout: PULL_TIMEOUT_MS });
     } else {
       console.log(`[DockerOrchestrator] Image ${image} already exists locally.`);
       // Optional: try to pull to update, but ignore failures
       try {
         console.log(`[DockerOrchestrator] Attempting to update image ${image}...`);
-        execFileSync("docker", ["pull", image], { stdio: "ignore" });
+        execFileSync("docker", ["pull", image], { stdio: "ignore", timeout: PULL_TIMEOUT_MS });
       } catch {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/docker.ts` around lines 120 - 137, Add an explicit timeout option to both
docker pull calls in the image-loading flow, using the existing missing-image
branch and update branch around imageExists. Keep failures from the
missing-image pull propagating to fail the run, while preserving the update
pull’s existing catch-and-warn behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/verify-local.ts`:
- Around line 375-400: Update the report validation flow around
isPlaywrightReport and extractFailures to count executed specs via a countSpecs
traversal, and reject reports with zero specs as infrastructure failures. Keep
parse errors handled as malformed reports, but perform the empty-report failure
check after the JSON.parse catch so that validation errors are not swallowed;
preserve execError precedence and existing failure extraction.

In `@src/docker.test.ts`:
- Around line 137-150: Update the inaccessible-directory test around
callEnsureWritableDir so it no longer relies on chmod 000, which is ineffective
for root and Windows runners. Mock or otherwise simulate the filesystem EACCES
failure encountered by ensureWritableDir, while preserving the assertion for the
clear “isn't writable/accessible” error and existing cleanup.

In `@src/docker.ts`:
- Around line 321-330: Update the ownership validation around actualOwner so
directory copies are checked recursively, validating every copied entry’s
ownership against expectedOwner rather than only containerPath. Preserve the
existing regular-file validation and error reporting, and use the existing
Docker execution flow to walk entries when localPath is a directory.

---

Nitpick comments:
In `@src/docker.ts`:
- Around line 293-302: Update the comment immediately above the docker exec call
to state that the destination directory is created using docker exec in the
running container. Remove the inaccurate reference to an ephemeral container or
conditional execution, and note that this operation requires the configured
container to be running.
- Around line 120-137: Add an explicit timeout option to both docker pull calls
in the image-loading flow, using the existing missing-image branch and update
branch around imageExists. Keep failures from the missing-image pull propagating
to fail the run, while preserving the update pull’s existing catch-and-warn
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fceb4230-856f-4cd4-8b1f-2ffc5742545c

📥 Commits

Reviewing files that changed from the base of the PR and between 166324f and 3d57825.

📒 Files selected for processing (12)
  • ops/vm/foundry-verify.service
  • ops/vm/foundry-verify.timer
  • ops/vm/verify-nightly.sh
  • package.json
  • scripts/close-resolved-issues.ts
  • scripts/monitor-releases.ts
  • scripts/verify-local.ts
  • scripts/version-utils.ts
  • src/cli/index.ts
  • src/docker.test.ts
  • src/docker.ts
  • verified-versions.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • package.json
  • ops/vm/foundry-verify.service
  • ops/vm/foundry-verify.timer
  • ops/vm/verify-nightly.sh
  • verified-versions.json
  • src/cli/index.ts
  • scripts/monitor-releases.ts

Comment thread scripts/verify-local.ts
Comment thread src/docker.test.ts
Comment thread src/docker.ts Outdated
- scripts/verify-local.ts: a well-formed Playwright report showing zero
  specs executed (empty test-file match, config issue) was silently
  recorded as a pass with zero failures - no evidence anything was
  actually verified. Added countSpecs() alongside the existing
  extractFailures() traversal; a zero count is now treated the same as a
  malformed report (execError takes precedence if present, otherwise a
  clear "zero specs executed" infrastructure-failure message).
- src/docker.ts: copyToContainer()'s post-copy ownership verification only
  checked the single top-level containerPath, never the nested contents of
  a directory copy - a regression that only got the top-level directory's
  ownership right (but not files underneath it) would go undetected. Now
  recursively verifies every entry via `find ... -exec stat` when the
  copied path is a directory (confirmed live against real Docker with a
  nested test directory), falling back to the original single-stat check
  for a plain file.
- src/docker.ts: added a 5-minute timeout to both docker pull calls in
  start() - previously a hung pull (bad network/registry issue) would
  block silently with no clear diagnosis, other than the systemd unit's
  blunt 6h TimeoutStartSec eventually killing everything.
- src/docker.ts: fixed a stale comment claiming the destination directory
  is created "via an ephemeral container or exec (if running)" - it's
  unconditionally a docker exec against the already-running container.
- src/docker.test.ts: replaced the ensureWritableDir permission-failure
  test's reliance on chmod 0o000 (unreliable when run as root, e.g. common
  CI containers, or on Windows) with a mocked EACCES on readdirSync/
  accessSync, so the test is deterministic regardless of who/where it
  runs. Updated the existing copyToContainer tests to use real temp files
  (needed since the fix above now calls fs.statSync on localPath) and
  added directory-copy test cases (matching and mismatched nested
  ownership).
@TheFehr
TheFehr merged commit e3e76fe into main Jul 31, 2026
2 checks passed
@TheFehr
TheFehr deleted the automate-vm-verification branch July 31, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant