Skip to content

Bind workflow operations to verified provider revisions - #280

Open
ihabadham wants to merge 32 commits into
mainfrom
ihabadham/feature/rolling-workflow-provider
Open

Bind workflow operations to verified provider revisions#280
ihabadham wants to merge 32 commits into
mainfrom
ihabadham/feature/rolling-workflow-provider

Conversation

@ihabadham

@ihabadham ihabadham commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Why

Shared workflow instructions, helper executables, and host plugin state could previously come from different revisions. A session could load a current skill while invoking an older local helper, or continue reading mutable provider files while an upgrade replaced them. Status and upgrade paths also disagreed about what "current" meant, particularly for linked worktrees, Claude commit-based plugins, and managed installations following the canonical provider branch.

This made failures such as merge-queue submission errors recur even though the shared pack already contained the correct helper. More importantly, it meant a long-running operation could not prove that all of its workflow assets came from one reviewed provider state.

What changed

Provider-bound operations

  • Add an operation registry with named skill, workflow, document, and executable assets.
  • Resolve every operation against one immutable per-revision Store snapshot.
  • Add begin, list, run, and release entrypoints with private state, schemas, shared/exclusive leases, explicit capacity limits, and reference-derived garbage collection.
  • Route registered capabilities, including merge submission and autonomous merge evaluation, through operation-owned runtime bundles rather than mutable checkout paths.
  • Contract-test canonical workflow routes so required shared skills cannot escape the registry boundary.

Pinned and managed profiles

  • Make pinned installations seed and use the exact committed revision without network access during operation begin.
  • Keep ordinary pinned workflows usable while rejecting capabilities that explicitly require a current managed provider.
  • Define managed current state as exactly shakacode/agent-workflows at refs/heads/main.
  • Fetch canonical main in an isolated, config-free Git repository using a trusted absolute Git executable and hardcoded URL/ref.
  • Import the verified commit into the immutable Store and bind receipt version, revision, and copied content to that same snapshot.
  • Make managed upgrades fetch once, fast-forward only clean local main, and reinstall the already-established revision without a second-fetch race.

Host and tool integrity

  • Let Claude Git installations use commit identities instead of a static 0.1.0 manifest version.
  • Record and validate Codex executable invocation and resolved paths for managed installs.
  • Run Codex provider inspection with a minimal unsetenv_others environment and bounded timeout; ambient AGENT_WORKFLOWS_CODEX_* variables are no longer authority.
  • Preserve a safe compatibility path for complete legacy pinned receipts while rejecting partial bindings.
  • Bind trusted Git and GitHub CLI executables into operation metadata and revalidate them before capability execution.
  • Run plan/dispatcher preflights plus CI-readiness and merge-assurance evidence through current-provider operation capabilities.
  • Copy fixed installation trust anchors into private capability bundles, bind them into provenance, and revalidate their live installation source before execution and lifecycle cleanup.

Upgrade and recovery safety

  • Recognize linked worktree sources correctly.
  • Scope backups to individual upgrade transactions.
  • Preserve verified backups when automatic rollback fails.
  • Serialize installation, lifecycle publication, execution, release, and garbage collection across processes.
  • Fail closed on malformed state, ambiguous provider roots, stale handles, swapped files, capacity exhaustion, and incomplete receipts.

User impact

Installed workflows can now operate in either of two explicit modes:

  • Pinned: stable, offline, exact committed source revision.
  • Managed: verified canonical main, refreshed through an exact provider contract.

An operation receives paths and runners from one immutable provider revision. Upgrading the installed pack no longer changes the files already bound to an active operation, and missing capabilities fail clearly instead of falling back to unrelated stale copies.

Review guide

The commits are arranged as incremental, validated layers:

  1. linked-worktree and Claude commit identity support;
  2. exact provider resolution and broken-source handling;
  3. immutable operation snapshots and registered capabilities;
  4. transaction-safe backup and rollback behavior;
  5. explicit lifecycle, leases, retention, and garbage collection;
  6. autonomous merge/runtime integration;
  7. usable pinned snapshots;
  8. canonical managed-main and Codex executable binding;
  9. cross-workflow route and receipt-identity contracts;
  10. provider-bound preflight/readiness/assurance evidence and installation trust.

The design rationale is documented in docs/plans/2026-07-25-bound-provider-snapshot-design.md.

Validation

Validated after integrating main at 36709f8d8649f6d50f0cf609896001ac9ea096d3 and completing the final provider-lifecycle review fixes at 98633dd11e1b68bb13166314c3092b6e67a5e774:

bin/validate
PASS agent-workflows validation
Inspecting 134 files
134 files inspected, no offenses detected

The final Linux run supplied a temporary shasum compatibility wrapper backed by Ruby's SHA-1 implementation because this machine does not install Perl's shasum; the affected test uses the digest only to compare before/after metadata identity.

Additional evidence developed during implementation includes:

  • hermetic proof that canonical fetch command construction and repository isolation are exact (the unit suite does not perform a live-network fetch);
  • pinned copy, symlink, dirty-worktree, and snapshot-repair cases;
  • managed fetch/upgrade single-fetch and source-mutation races;
  • Codex hostile-environment, path-resolution, timeout, and legacy-receipt cases;
  • operation publication/execution/release concurrency races;
  • lifecycle capacity and garbage-collection failure cases;
  • transaction rollback and backup-identity cases;
  • provider-operation route contract coverage;
  • signed dispatcher confirmation through a bound installation trust anchor;
  • hostile-PATH proof that readiness and assurance use the recorded GitHub executable;
  • lifecycle list/release proof for capability bundles containing installation trust;
  • hosted-toolcache regression proof using a deliberately 0777 active Ruby interpreter, a 0500 operation snapshot, real capability execution, and interpreter-swap rejection;
  • schema-one operation compatibility proof across list and release after the runtime upgrade;
  • three consecutive full operation-suite runs under Ruby 3.4.8 after disabling background Git maintenance in the fixture source and receive-side maintenance in its bare remote (243 runs, 1,422 assertions, zero failures/errors);
  • real shallow-clone reproduction showing Git's rejected private ref without --update-shallow, followed by exact-ref import success and a regression test for pinned imports from shallow provider checkouts;
  • no-external-command reproduction matching GitHub's Ubuntu runner, plus lifecycle coverage using the POSIX shell builtin explicitly;
  • close-on-exec lifecycle-descriptor proof plus trusted non-exec guardian coverage for normal exit, TERM, KILL, SIGPIPE, delayed signal handling, runner crash, and operation-launcher crash;
  • explicit capability-environment coverage proving arbitrary and dynamic-loader variables are removed while GitHub authentication, proxy, and certificate connectivity remain available;
  • bounded secure-Git process-group termination, resolver-crash lease retention, and resolver-crash-plus-stderr-flood proof that the guardian cannot deadlock before cleanup;
  • atomic publication for every installed helper, including copy/symlink upgrades over directory-pointing symlinks without write-through or a pre-delete gap;
  • contract coverage that derives helper directories from returned skill-file paths rather than treating SKILL.md as a directory;
  • independent task review: specification PASS and code quality PASS;
  • iterative isolated whole-branch adversarial review after rebase: three fix rounds followed by No meaningful issues found.

Summary by CodeRabbit

  • New Features
    • Added provider-aware installation, upgrades, status checks, and operation workflows for Codex and Claude.
    • Added immutable snapshots, capability discovery, operation listing/release, and secure lifecycle management.
    • Added trusted executable binding, managed or pinned sources, provider freshness checks, and fail-closed asset resolution.
  • Bug Fixes
    • Improved protection against unsafe paths, tampered files, hostile environments, invalid locks, and forged recovery state.
    • Claude manifests no longer require version metadata.
  • Documentation
    • Updated installation, lifecycle, provider-binding, restart, and workflow guidance.
  • Tests
    • Expanded security, lifecycle, installation, upgrade, provider, schema, and source-validation coverage.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The change adds provider-bound operation snapshots, secure lifecycle leases, validated executable bindings, managed and pinned provider handling, operation runners, immutable storage, strict schemas, and updates across workflows, skills, documentation, and tests.

Changes

Provider operation foundation

Layer / File(s) Summary
Secure lifecycle and storage
bin/agent_workflows_operation/*, bin/agent-workflows-lifecycle*
Adds secure paths, Git-backed snapshots, lifecycle leases, operation publication, identity checks, capacity limits, cleanup, and signal-aware process supervision.
Provider resolution and execution
bin/agent_workflows_operation/provider.rb, resolver.rb, runner.rb, bin/agent-workflows-resolve, bin/agent-workflows-run
Adds provider validation, capability resolution, operation handles, runner commands, environment sanitization, and execution-time integrity checks.
Installation and upgrade integration
bin/install-agent-workflows, bin/upgrade-agent-workflows, bin/agent-workflows-status, bin/agent-workflows-delivery-state
Adds pinned and managed profiles, canonical Git validation, trusted executable bindings, snapshot-based copying, atomic publication, rollback identity checks, and bounded Codex inspection.
Workflow and skill contracts
operation-capabilities.json, schemas/*, skills/*, workflows/*, docs/*
Routes assets and capabilities through retained provider operations, adds release and recovery rules, and replaces repository-local fallback paths with returned asset paths.
Validation coverage
bin/*test*, test/*, .github/workflows/validate.yml
Adds contract, lifecycle, operation, source, installation, executable, provenance, schema, and environment-isolation tests. Validation now checks the pinned JSON Schemer version.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: justin808

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: binding workflow operations to verified provider revisions.
✨ 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 ihabadham/feature/rolling-workflow-provider

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@ihabadham
ihabadham force-pushed the ihabadham/feature/rolling-workflow-provider branch from cdd721d to d8f6f22 Compare July 31, 2026 20:59
@ihabadham
ihabadham marked this pull request as ready for review July 31, 2026 20:59
Comment thread bin/agent_workflows_operation/lifecycle_lease.rb Outdated
Comment thread bin/agent_workflows_operation/process_supervisor.rb Outdated
Comment thread bin/agent_workflows_operation/provider.rb
Comment thread bin/agent_workflows_operation/lifecycle_lease.rb Outdated
Comment thread bin/agent_workflows_operation/process_supervisor.rb Outdated
Comment thread bin/agent_workflows_operation/secure_git.rb
Comment thread skills/pr-batch/bin/autonomous-merge-eligibility
Comment thread workflows/adversarial-pr-review.md Outdated
@claude

claude Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review: PR #280 — Bind workflow operations to verified provider revisions

This is a large PR (98 files, ~11.8k additions) introducing a new "operation" subsystem so skill/workflow/executable assets resolve against one immutable per-revision snapshot instead of mutable checkout paths, plus a managed/pinned install split, lifecycle/lease/GC machinery, and routing of autonomous-merge decisions through it. I reviewed it in parallel across the core resolver/store, security-sensitive git/path handling, lifecycle/lease concurrency, install/upgrade scripts, the pr-batch merge-decision code, and the doc/skill glue.

Overall, this is unusually defensive code for its class — argv-array subprocess calls throughout (no shell-string interpolation found), Git-tree-hash verification of on-disk snapshots, PATH-independent trusted-executable resolution, atomic stage-then-rename state publication, and genuinely non-happy-path test coverage in most of the security-critical files (symlink/traversal attacks, GC races, forged reentry tokens, malformed state). The concrete issues found are narrow, mostly at the edges of an otherwise careful design, not systemic.

Findings posted inline

  • bin/agent_workflows_operation/lifecycle_lease.rb:46 (and the same pattern in bin/agent-workflows-resolve:47) — the lifecycle lock's fd has close_on_exec cleared unconditionally, so it leaks into git/capability subprocesses spawned while the lease is held. Since flock is per open-file-description, code in that child can release the parent's lease early via flock(LOCK_UN) on the inherited fd — undermining the exclusion the lease exists to provide. This pattern looks copied from agent-workflows-lifecycle's reentry protocol without the matching justification (no reentry-token/liveness verification here, and no test exercises inherited fds).
  • bin/agent_workflows_operation/process_supervisor.rb:15 — the capability subprocess is spawned without unsetenv_others: true, unlike other external-process calls in this PR; ambient vars like LD_PRELOAD/PYTHONPATH pass through unfiltered into the otherwise-identity-verified executable.
  • bin/agent_workflows_operation/secure_git.rb:147 — no timeout anywhere in the git call chain (including the canonical-main network fetch), unlike other parts of the codebase that bound git probes with a timeout.
  • skills/pr-batch/bin/autonomous-merge-eligibility:80-95 — the legacy --trusted-helper-provenance path resolves git from PATH with none of the absolute-path/executable checks applied on the provider-operation path, even though the ADR says this legacy path should only be reachable for diagnostics/compatibility tests — nothing in the code actually restricts it that way.
  • workflows/adversarial-pr-review.md:76-78 (and prose at 143-144) — stale leftover from the pre-rework direct-path model: a PR_BATCH_SKILL_DIR guard is set but never used, and prose describes pr-ci-readiness as resolved from PR_BATCH_SKILL_DIR when it's actually invoked via AGENT_WORKFLOWS_RUNNER.

Additional findings (not on lines included in the diff hunks, so noted here)

  • bin/upgrade-agent-workflows update_source_clone (pinned/--source upgrade path) — does git fetch + merge --ff-only with no dirty-worktree check, unlike the managed path's AgentWorkflowsSourceContract.fast_forward_main!, which explicitly requires a clean tree first. A maintainer upgrading from their own dev checkout with uncommitted changes gets local main silently fast-forwarded with no warning.
  • skills/pr-batch/bin/merge-assurance (MAX_EVIDENCE_AGE_SECONDS = 300) — merge assurance validates internal consistency and freshness (≤300s) of embedded CI evidence, but nothing in the submission path re-queries gh for live CI state at merge time. A required check flipping pending→fail within that 5-minute window wouldn't be caught. This may be an intentional, documented tradeoff (the ADR only requires the eligibility verdict be recomputed immediately before merge) but it's worth confirming that's the intended boundary between "verified as of collection" and "true right now."
  • bin/agent_workflows_operation/tree.rb filesystem_entry — non-regular/non-symlink filesystem entries (FIFOs, sockets, device nodes) are silently dropped (mapped to nil) rather than raising, so verify_against_git!'s key-based comparison wouldn't notice such an entry appearing in an otherwise-verified tree. Narrow, but inconsistent with how strict the rest of this file is.
  • bin/agent_workflows_operation/store.rb — in stage_snapshot!, the EEXIST/ENOTEMPTY rename-race branch skips the remove_repairable_store! fallback that the earlier File.exist? branch uses, so a process that hits this specific timing loses the intended repair-and-retry behavior. Narrow window, low severity.
  • test/agent_stack/support.bash:7-15 — the EXIT trap's cleanup loop runs under set -euo pipefail; if SecurePaths.cleanup_owned_directory! raises for one registered temp dir (identity mismatch), the whole trap aborts and later temp dirs plus the registry file itself never get cleaned up. Test-only, low impact.

Minor/code-quality nits (no action likely needed)

  • bin/agent_workflows_operation/resolver.rb#begin! re-reads/parses the install metadata JSON multiple times per call instead of threading one snapshot through — not a bug today (everything runs under the single exclusive lease) but needless I/O and a latent trap if a future call path ever bypasses the lease.
  • bin/agent_workflows_operation/secure_git.rb#archive! writes all tar entries before calling validate_snapshot_symlinks! once at the end, rather than validating as it goes; currently not exploitable (git's tree model prevents a name from being both blob and tree) but a fragile ordering, and bin/agent-workflows-source-contract-test.rb has no adversarial coverage of this method's symlink handling — worth adding given it's one of the more security-sensitive code paths.
  • tree.rb's verify_file_against_git!/verify_path_against_git! are near-duplicates.

What held up well

  • Trusted Git/gh executable resolution is genuinely PATH-independent on the provider-operation path (fixed absolute candidates, device/inode/size/sha256 pinning re-verified before every run).
  • Fetch-once claim for managed upgrades checks out: install-agent-workflows uses cached_revision! (no second fetch) when invoked with --no-fetch from upgrade-agent-workflows.
  • Rollback/backup safety matches the PR description: backups are scoped per-transaction, verified by dev/ino/uid before restore, and preserved (not deleted) if rollback itself fails.
  • Legacy pinned-receipt handling correctly rejects partial bindings (one of codex_executable/codex_executable_resolved present without the other) while accepting complete legacy receipts.
  • autonomous_merge_evidence.rb's ABA force-push detection (sorted timeline-event-ID watermark + exact updated_at equality across the read window) and pr-ci-readiness's exact-head-SHA binding are solid, with tests for exactly the race conditions they're meant to close.
  • The 98-file doc/skill sweep (workflows/.md, skills//SKILL.md, docs/.md) found the codebase's capability names and assets. references are internally consistent with the new registry, aside from the one stale adversarial-pr-review.md reference noted above.

No blocking correctness bugs found in the core snapshot-immutability/GC-reference-counting model itself (which has real, non-trivial test coverage for the race conditions it's designed to prevent). The lock-fd-leak finding is the one I'd want addressed before merge given it touches the concurrency guarantee the whole subsystem is built on; the rest are worth triaging but lower urgency.

@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 (7)
bin/agent_workflows_operation/secure_git.rb (2)

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

Consider renaming the private capture3.

The name matches Open3.capture3, but the semantics differ. This method enforces @timeout, creates a process group, and raises GitError on timeout. A reader at the call sites on Lines 71 and 164 can assume plain Open3 behavior and miss the timeout path.

A name such as capture_with_guardian states the added behavior.

🤖 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 `@bin/agent_workflows_operation/secure_git.rb` at line 176, Rename the private
capture3 method to a name that communicates its timeout and process-group
safeguards, such as capture_with_guardian, and update every internal call site
including the uses near the reported lines. Preserve the existing command
execution and GitError timeout behavior.

239-252: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the timeout parameter to distinguish it from @timeout.

In this method timeout is the pipe write end (used at Line 252), and @timeout is the duration in seconds (used at Line 240). The two names differ only by the instance-variable sigil, and both appear in the same control path.

Rename the parameter to timeout_writer, and update the keyword at Line 224 and Line 210.

🤖 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 `@bin/agent_workflows_operation/secure_git.rb` around lines 239 - 252, Rename
the wait_for_process_group! parameter from timeout to timeout_writer, update its
timeout.write usage, and adjust all callers and keywords at the referenced call
sites to pass timeout_writer while preserving `@timeout` as the duration.
bin/agent_workflows_operation/process_supervisor.rb (1)

55-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated mirror_status in two guardian implementations. Both files implement the same signal-mirroring contract with byte-identical bodies: exit with exitstatus when the child exited, otherwise restore SYSTEM_DEFAULT for the terminating signal, re-raise it, and fall back to 128 + signal. The shared root cause is one contract expressed twice, so a later correction to one copy can leave the other stale.

  • bin/agent_workflows_operation/process_supervisor.rb#L55-L66: extract this body into one shared helper that both guardians call.
  • bin/agent_workflows_operation/secure_git.rb#L296-L307: remove this copy and call the shared helper. SecureGit already references AgentWorkflowsOperation constants, so the helper is reachable without a new dependency.
🤖 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 `@bin/agent_workflows_operation/process_supervisor.rb` around lines 55 - 66,
The mirror_status implementation is duplicated across both guardians and should
be centralized. In bin/agent_workflows_operation/process_supervisor.rb lines
55-66, extract the existing signal-mirroring body into one shared
AgentWorkflowsOperation helper; in bin/agent_workflows_operation/secure_git.rb
lines 296-307, remove SecureGit’s duplicate mirror_status body and call that
helper instead, preserving the existing exitstatus, signal restoration,
re-raise, and fallback behavior.
bin/agent-workflows-operation-test.rb (4)

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

Do not signal a process group through an unverified leader pid.

Line 477 sends KILL to the group id -git_pid. The test never asserts that git_pid leads its own process group, unlike the sibling test at Line 412. If git_pid is not a group leader, this call signals an unrelated process group in CI. Signal the process directly, or assert group leadership first.

♻️ Proposed cleanup change
   ensure
-    Process.kill("KILL", -git_pid) if git_pid && process_alive?(git_pid)
+    Process.kill("KILL", git_pid) if git_pid && process_alive?(git_pid)
     Process.kill("KILL", guardian_pid) if guardian_pid && process_alive?(guardian_pid)
     Process.kill("KILL", resolver_pid) if resolver_pid && process_alive?(resolver_pid)
🤖 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 `@bin/agent-workflows-operation-test.rb` around lines 477 - 478, Update the
cleanup block around git_pid and guardian_pid so it does not signal an
unverified process group: replace the negative git_pid target with
direct-process signaling, or add the same process-group-leader assertion used by
the sibling test before signaling the group. Preserve the existing liveness
checks and guardian cleanup behavior.

2095-2099: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fail with a clear message when procfs does not yield a parent pid.

If the target process exits before Line 2096, File.read raises Errno::ENOENT. If the regex does not match, Line 2098 raises NoMethodError on nil. The only caller kills processes around this call, so both cases are reachable. Raise an explicit failure instead.

♻️ Proposed helper hardening
   def process_parent(pid)
-    status = File.read("/proc/#{pid}/status")
-    match = status.match(/^PPid:\s+(\d+)$/)
-    Integer(match[1], 10)
+    status = begin
+      File.read("/proc/#{pid}/status")
+    rescue Errno::ENOENT
+      flunk "process #{pid} exited before its parent could be read"
+    end
+    match = status.match(/^PPid:\s+(\d+)$/)
+    flunk "no PPid entry for process #{pid}" unless match
+    Integer(match[1], 10)
   end
🤖 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 `@bin/agent-workflows-operation-test.rb` around lines 2095 - 2099, Harden
process_parent so failures reading /proc or parsing the PPid entry raise a clear
explicit error instead of exposing Errno::ENOENT or nil indexing. Handle
File.read failures for an exited target process and validate the match before
calling Integer, while preserving the existing parent-PID return path.

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

Wait for parsable pid content, not for file existence. All three sites treat File.file?(ready) as the readiness signal, but the fixture creates the file and writes the pids in a separate step. A read that lands between those steps returns an empty or partial string, and Integer then raises TypeError or ArgumentError instead of failing on the behavior under test. Gate each wait on successfully parsed content.

  • bin/agent-workflows-operation-test.rb#L400-L401: wait until File.read(ready).split.length == 2, then parse git_pid and guardian_pid.
  • bin/agent-workflows-operation-test.rb#L459-L460: apply the same content check before parsing the flood pids.
  • bin/agent-workflows-operation-test.rb#L1034-L1035: wait until File.read(ready) matches /\A\d+\s*\z/ before calling Integer for capability_guardian.
♻️ Proposed readiness helper
def wait_for_pids(path, count)
  values = nil
  assert(wait_until(timeout: 5) do
    values = File.file?(path) ? File.read(path).split : []
    values.length == count && values.all? { |value| value.match?(/\A\d+\z/) }
  end, "#{path} never reported #{count} pid(s)")
  values.map { |value| Integer(value, 10) }
end
🤖 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 `@bin/agent-workflows-operation-test.rb` around lines 400 - 401, Replace
file-existence readiness checks with successful PID-content checks before
parsing: at bin/agent-workflows-operation-test.rb#L400-L401 wait for exactly two
valid PID tokens before assigning git_pid and guardian_pid; apply the same
validation at `#L459-L460` before parsing the flood PIDs; at `#L1034-L1035` wait for
content matching a single numeric PID before converting it to
capability_guardian. Reuse a shared readiness helper if appropriate.

354-360: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use SecureGit directly or avoid private instance-variable coupling.

AgentWorkflowsOperation::SecureGit#initialize accepts timeout: and sets @executable from known Git candidates, so these tests can instantiate SecureGit directly with timeout: if they use the system Git selector, or define a test double that does not depend on private @executable/@timeout names. If a helper remains, put it in one place so the constructor shape is duplicated once instead of three times.

🤖 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 `@bin/agent-workflows-operation-test.rb` around lines 354 - 360, Update the
test setup around AgentWorkflowsOperation::SecureGit to avoid overriding its
initialize method and coupling the tests to private `@executable` and `@timeout`
variables. Instantiate SecureGit directly with the existing timeout: argument
where the system Git selector is suitable, or centralize a helper/test double in
one place if custom executables are required, so the constructor shape is
duplicated only once.
🤖 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 `@bin/agent_workflows_operation/secure_git.rb`:
- Around line 268-273: Bound the post-KILL wait loop in the surrounding
process-termination method using the class’s existing monotonic-time deadline
pattern, so it exits after the configured limit even if
process_group_alive?(pid) remains true. After this bounded wait, replace the
conditional reap_blocking(pid) call with a nonblocking reap to avoid blocking
when the leader is still running.
- Around line 192-199: Bound the reader thread wait in the main flow around
wait_for_process_group! by replacing the unbounded readers.each(&:join) with
joins using TERMINATION_GRACE_SECONDS, matching the existing ensure behavior.
Retrieve readers’ values defensively after the bounded joins so incomplete or
terminated reader threads cannot cause an additional blocking wait, while
preserving the existing timeout error and returned stdout, stderr, and status.

---

Nitpick comments:
In `@bin/agent_workflows_operation/process_supervisor.rb`:
- Around line 55-66: The mirror_status implementation is duplicated across both
guardians and should be centralized. In
bin/agent_workflows_operation/process_supervisor.rb lines 55-66, extract the
existing signal-mirroring body into one shared AgentWorkflowsOperation helper;
in bin/agent_workflows_operation/secure_git.rb lines 296-307, remove SecureGit’s
duplicate mirror_status body and call that helper instead, preserving the
existing exitstatus, signal restoration, re-raise, and fallback behavior.

In `@bin/agent_workflows_operation/secure_git.rb`:
- Line 176: Rename the private capture3 method to a name that communicates its
timeout and process-group safeguards, such as capture_with_guardian, and update
every internal call site including the uses near the reported lines. Preserve
the existing command execution and GitError timeout behavior.
- Around line 239-252: Rename the wait_for_process_group! parameter from timeout
to timeout_writer, update its timeout.write usage, and adjust all callers and
keywords at the referenced call sites to pass timeout_writer while preserving
`@timeout` as the duration.

In `@bin/agent-workflows-operation-test.rb`:
- Around line 477-478: Update the cleanup block around git_pid and guardian_pid
so it does not signal an unverified process group: replace the negative git_pid
target with direct-process signaling, or add the same process-group-leader
assertion used by the sibling test before signaling the group. Preserve the
existing liveness checks and guardian cleanup behavior.
- Around line 2095-2099: Harden process_parent so failures reading /proc or
parsing the PPid entry raise a clear explicit error instead of exposing
Errno::ENOENT or nil indexing. Handle File.read failures for an exited target
process and validate the match before calling Integer, while preserving the
existing parent-PID return path.
- Around line 400-401: Replace file-existence readiness checks with successful
PID-content checks before parsing: at
bin/agent-workflows-operation-test.rb#L400-L401 wait for exactly two valid PID
tokens before assigning git_pid and guardian_pid; apply the same validation at
`#L459-L460` before parsing the flood PIDs; at `#L1034-L1035` wait for content
matching a single numeric PID before converting it to capability_guardian. Reuse
a shared readiness helper if appropriate.
- Around line 354-360: Update the test setup around
AgentWorkflowsOperation::SecureGit to avoid overriding its initialize method and
coupling the tests to private `@executable` and `@timeout` variables. Instantiate
SecureGit directly with the existing timeout: argument where the system Git
selector is suitable, or centralize a helper/test double in one place if custom
executables are required, so the constructor shape is duplicated only once.
🪄 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 Plus

Run ID: e9411530-3fce-49a2-9cb5-dead54abcc9a

📥 Commits

Reviewing files that changed from the base of the PR and between 70d7f4f and c68e774.

📒 Files selected for processing (14)
  • bin/agent-workflows-lifecycle-test.rb
  • bin/agent-workflows-operation-test.rb
  • bin/agent-workflows-resolve
  • bin/agent-workflows-run
  • bin/agent_workflows_operation/lifecycle_lease.rb
  • bin/agent_workflows_operation/process_supervisor.rb
  • bin/agent_workflows_operation/runner.rb
  • bin/agent_workflows_operation/secure_git.rb
  • bin/agent_workflows_operation/state.rb
  • bin/provider-operation-contract-test.rb
  • docs/host-adapter/contract.md
  • docs/installation-and-upgrades.md
  • docs/plans/2026-07-25-bound-provider-snapshot-design.md
  • workflows/adversarial-pr-review.md
🚧 Files skipped from review as they are similar to previous changes (10)
  • bin/agent-workflows-run
  • bin/provider-operation-contract-test.rb
  • workflows/adversarial-pr-review.md
  • docs/installation-and-upgrades.md
  • bin/agent-workflows-resolve
  • docs/plans/2026-07-25-bound-provider-snapshot-design.md
  • bin/agent_workflows_operation/lifecycle_lease.rb
  • bin/agent_workflows_operation/runner.rb
  • bin/agent_workflows_operation/state.rb
  • docs/host-adapter/contract.md

Comment thread bin/agent_workflows_operation/secure_git.rb Outdated
Comment thread bin/agent_workflows_operation/secure_git.rb Outdated
Comment thread bin/upgrade-agent-workflows Outdated
Comment thread bin/agent_workflows_operation/secure_git.rb
Comment thread bin/agent_workflows_operation/resolver.rb Outdated
Comment thread bin/agent_workflows_operation/source_contract.rb
Comment thread bin/install-agent-workflows Outdated
Comment thread bin/agent_workflows_operation/registry.rb
Comment thread skills/pr-batch/bin/pr-ci-readiness
Comment thread skills/pr-batch/lib/autonomous_merge_runtime_trust.rb
Comment thread skills/pr-batch/bin/pr-merge-submit Outdated
@claude

claude Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review summary — PR 280 (Bind workflow operations to verified provider revisions)

This is a large (98 files, ~12.4k additions) rework introducing an immutable, provider-bound "operation" registry for skills/workflows/executables, plus a pinned/managed install split and hardened process/git primitives. I reviewed it across four areas in parallel (core registry/store/resolver, security-critical process/git/path primitives, install/upgrade/status, and the autonomous merge capability scripts). Overall the design is unusually rigorous for this kind of infrastructure: real content-hash verification against Git objects, symlink-ancestor traversal checks, close-on-exec lease descriptors backed by real /proc/self/fd tests, array-form subprocess invocation everywhere (no shell injection surface found anywhere in scope), and consistent unsetenv_others: true hardening at trusted subprocess boundaries. I did not find any place where a registered capability falls back to reading from a mutable checkout path instead of the immutable snapshot, which was the core integrity property this PR set out to establish.

I have left inline comments on the specific, actionable findings. Summary, most severe first:

High severity

  • bin/upgrade-agent-workflows:526-527 — cleanup_backup runs before completed=true is set, under set -euo pipefail. A transient/unrelated cleanup failure (e.g. a backup-identity mismatch or a locked/permission-denied rm -rf) after a fully successful install will trigger the EXIT traps finish_upgrade, which sees completed != true and performs a full rollback of a successful upgrade. See inline comment for a suggested fix (set completed=true right after the install succeeds, before attempting cleanup).
  • bin/agent_workflows_operation/secure_git.rb (archive!/extract_archive_entry!/validate_snapshot_symlinks!, ~lines 95-388) — symlink-escape validation (validate_snapshot_symlinks!) runs after all tar entries have already been extracted to disk via FileUtils.mkdir_p/File.open, which follow symlinks transparently. A crafted archive (reachable via both the managed canonical fetch and Store#import_local! for local revisions) can write a file outside destination before the post-hoc check rejects the snapshot. The rejection stops the tampered revision from being trusted, but not the side-effecting write. No test currently exercises this path with an adversarial archive.

Medium severity

  • bin/agent_workflows_operation/resolver.rb:27 — open_pinned_snapshot! runs before lifecycle.gc!, and gc! independently re-reads the installed revision, creating a TOCTOU window with a concurrent install/upgrade. Fails closed (raises StoreError) but is an untested race; consider deferring pinned-snapshot resolution until after gc!.
  • bin/agent_workflows_operation/source_contract.rb:92 (fetch!) — the canonical-main network fetch uses plain Open3.capture3 with no timeout and no process-group supervision, unlike SecureGit#run!s bounded, guardian-supervised equivalent used elsewhere. A stalled fetch can hang install/upgrade indefinitely.

Low severity / consistency / test coverage

  • bin/install-agent-workflows:599 — predictable $$.$RANDOM staging name instead of mktemp, inconsistent with the adjacent publish_entry_copy.
  • bin/agent_workflows_operation/registry.rb:41 — hardcodes the specific capability name pr-merge-submit, coupling an otherwise generic registry abstraction to one product feature.
  • skills/pr-batch/bin/pr-ci-readiness:372 (resolve_gh_executable!) — the fail-closed bound-gh-executable check has no test coverage, unlike the equivalent in merge-assurance which has an explicit hostile-PATH regression test. Since this component decides "CI is green" for autonomous merge, a future regression here would not be caught.
  • skills/pr-batch/lib/autonomous_merge_runtime_trust.rb:96 (verify_provider_operation) — none of the actual tamper-detection branches (digest mismatch, path mismatch, source outside allowlist, malformed manifest) have regression tests; only the happy path and one deleted-manifest case are covered.
  • skills/pr-batch/bin/pr-merge-submit:704 — the bound gh executable check only verifies a leading /, unlike merge-assurance/pr-ci-readiness which also check File.file?/File.executable?; an EACCES would surface as an uncaught SystemCallError rather than the modules own Error type. Still fails closed, just a rougher error surface.

Other observations (not filed inline, lower confidence/impact)

  • bin/agent_workflows_operation/store.rbs verify_store! re-hashes every file and re-runs git ls-tree multiple times per begin! call (via gc!s double inventory plus resolver.rbs explicit third call), with no in-process caching — a performance concern under frequent operation churn, not a correctness one.
  • runner.rb/state.rb duplicate the capability_digest/legacy_runtime_digest computation between publisher and verifier — risk of silent drift if one is edited without the other.
  • bin/agent_workflows_operation/process_supervisor.rbs guardian-side signal trap drops signals once the capability process is spawned (no forwarding branch, unlike the outer wait!s trap). In the current codebase this is masked because all callers signal via process group rather than by PID, so it is not currently reachable, but it is worth an explicit forwarding branch for robustness against future direct-PID signaling.
  • skills/pr-batch/lib/autonomous_merge_evidence.rbs gh_api reads AGENT_WORKFLOWS_GH_EXECUTABLE directly without the absolute-path/executable-bit validation applied in merge-assurance/pr-ci-readiness. Not attacker-reachable today (always set by the trusted runner), but an inconsistent trust check relative to its siblings.
  • test/agent_doctor/install_ownership_test.rb does not exercise the symlink-pruning branch of InstallOwnership.digest or the uid-mismatch rejection in portable_mode — a coverage gap relative to that files own security logic, not a bug.
  • Two concurrent upgrades of different targets sharing the same managed --source clone can race on git fetch/merge --ff-only against one working tree, since the exclusive lease is scoped per-target rather than per-source-clone. The "fetch once, no second-fetch race" guarantee is correctly proven within a single invocation, just not across concurrent processes sharing a source.

What looks solid

  • No command/argument injection found anywhere in scope — all git/gh subprocess invocations use array-form arguments with regex-validated refs/revisions, never shell-interpolated strings.
  • GIT_CANDIDATES/trusted executable resolution use hardcoded absolute-path allowlists, genuinely immune to PATH hijacking.
  • secure_paths.rbs ancestor-symlink/ownership checks, atomic write_json! (temp+EXCL+fsync+rename), and lifecycle_lease.rbs close-on-exec lock descriptor are well constructed and backed by real adversarial tests (hostile symlink substitution, exec-boundary FD checks).
  • Pinned vs managed install separation is correctly wired: pinned installs never touch the network; managed upgrades fetch exactly once per invocation, verified by test.
  • The autonomous-merge eligibility/evidence/readiness logic is consistently fail-closed — every anomaly path traced routes to "unknown"/rejection rather than silently passing, and PR mutation mid-evaluation (head/base/commit-count changes) is explicitly detected and blocked.

Given the size and security sensitivity of this change, I would suggest at minimum addressing the two high-severity findings (upgrade rollback-on-cleanup-failure, and archive symlink-escape-before-validation) before merge, and adding the missing adversarial tests for the runtime-trust and CI-readiness executable-binding checks given how central they are to the autonomous-merge safety story.

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