Skip to content

chore: sync workflow templates - #860

Merged
stranske-keepalive[bot] merged 1 commit into
mainfrom
sync/workflows-536a715df8b0
Jun 20, 2026
Merged

chore: sync workflow templates#860
stranske-keepalive[bot] merged 1 commit into
mainfrom
sync/workflows-536a715df8b0

Conversation

@stranske

@stranske stranske commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Sync Summary

Files Updated

  • agents-81-gate-followups.yml: Gate followups hub - consolidates keepalive and autofix followups
  • agents-73-codex-belt-conveyor.yml: Codex belt conveyor - orchestrates belt worker execution and handles completion
  • agents-guard.yml: Agents guard - enforces agents workflow protections (Health 45)
  • check_deliberate_break.py: Opt-in Gate helper that proves named deliberate-break acceptance tests fail against the base implementation
  • runtime_ac_merge_guard.js: Blocks external merge lanes for PRs that require local Orchestrator runtime acceptance checks
  • gate_summary.py: Gate summary renderer - generates PR gate check summary
  • AGENTS.md: Context file for agents and coding assistants
  • CLAUDE.md: Context file for Claude/AI assistants

Files Skipped

  • pr-00-gate.yml: File exists and sync_mode is create_only
  • ci.yml: File exists and sync_mode is create_only
  • renovate.json: File exists and sync_mode is create_only
  • cross-repo-smoke.yml: File exists and sync_mode is create_only
  • llm_slots.json: None

Review Checklist

  • CI passes with updated workflows
  • No repo-specific customizations were overwritten

Source: stranske/Workflows
Source SHA: deacb8ee2852a7c22fe229645468776f35921628
Template hash: 536a715df8b0
Sync branch: sync/workflows-536a715df8b0
Consumer repo: stranske/Collab-Admin
Manifest: .github/sync-manifest.yml

Summary by CodeRabbit

  • New Features

    • Added test-quality gate result tracking to the CI/CD pipeline reporting system
    • Implemented runtime acceptance criteria merge guards for PR merge workflows
    • Added deliberate break validation script for testing acceptance criteria
  • Documentation

    • Added "Critical Evaluator" working guidance to agent instructions, emphasizing judgment over agreement and explicit problem reporting
  • Chores

    • Updated API client version in workflow configuration

Automated sync from stranske/Workflows
Template hash: 536a715df8b0

Changes synced from sync-manifest.yml
@stranske stranske added sync Automated sync from Workflows automated Automated sync from Workflows labels Jun 20, 2026
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds scripts/check_deliberate_break.py for execution-based deliberate-break acceptance checking and .github/scripts/runtime_ac_merge_guard.js to gate merges on runtime AC labels. Integrates both into two agent workflows, extends gate_summary.py with a test-quality result, updates an external action pin in agents-guard.yml, and adds critical-evaluator guidance to AGENTS.md and CLAUDE.md.

Changes

AC Enforcement Gates

Layer / File(s) Summary
Deliberate-break spec parsing and data model
scripts/check_deliberate_break.py
Defines DeliberateBreakSpec, verdict constants, regex patterns, GitHub Actions output helpers, an explicit marker parser, a fallback heuristic parser, and the top-level parse_deliberate_break_spec() orchestrator.
Deliberate-break verification engine
scripts/check_deliberate_break.py
Implements subprocess helpers, tamper detection via assertion diff, _archive_ref() with path-traversal guard, and verify_spec() which runs the test at HEAD, snapshots base, and returns PASS/HOLLOW/BROKEN verdicts.
Deliberate-break CLI entry point
scripts/check_deliberate_break.py
Implements main() with argument parsing, PR-body acquisition, marker detection, GitHub output emission, JSON printing, and exit-code mapping.
Runtime AC merge guard module
.github/scripts/runtime_ac_merge_guard.js
Defines RUNTIME_AC_REQUIRED_LABELS, label normalization/detection helpers, fetchPullRequestLabels via GitHub REST API, and assertRuntimeAcMergeAllowed which throws a coded error when required labels are present.
Workflow merge-gate integration
.github/workflows/agents-73-codex-belt-conveyor.yml, .github/workflows/agents-81-gate-followups.yml
Adds runtime_ac_merge_guard.js to sparse checkouts and calls assertRuntimeAcMergeAllowed before each workflow's squash/guarded merge API call.
Gate summary test-quality gate
.github/scripts/gate_summary.py
Adds test_quality_result field to SummaryContext, reads TEST_QUALITY_RESULT env var in build_context(), threads it through _active_lines/_append_job_table as a new table row, and maps cancelled/non-success values to pending/failure states.

Infrastructure and Agent Guidance

Layer / File(s) Summary
agents-guard.yml external action SHA update
.github/workflows/agents-guard.yml
Updates the pinned commit SHA for stranske/Workflows setup-api-client action in both pull_request_target and pull_request fallback steps.
Critical-evaluator guidance
AGENTS.md, CLAUDE.md
Adds a "Working Stance — Critical Evaluator (read first)" section to both files with instructions on evaluation, confidence reporting, and handling disagreements.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(100, 149, 237, 0.5)
        Note over Workflow,GitHub API: Merge gate flow (agents-73 / agents-81)
    end
    participant Workflow as Workflow merge step
    participant guard as assertRuntimeAcMergeAllowed
    participant GitHub API as GitHub REST API
    participant check as check_deliberate_break.py

    Workflow->>guard: call(owner, repo, prNumber, source)
    guard->>GitHub API: pulls.listLabelsOnIssue(owner, repo, prNumber)
    GitHub API-->>guard: label list
    guard->>guard: runtimeAcRequirement(labels)
    alt runtime AC labels present
        guard-->>Workflow: throw Error(code=runtime_ac_merge_blocked)
    else no runtime AC labels
        guard-->>Workflow: return allowed
        Workflow->>GitHub API: pulls.merge(squash)
    end

    rect rgba(144, 238, 144, 0.5)
        Note over check,GitHub API: Deliberate-break check (CI job)
    end
    check->>check: parse_deliberate_break_spec(pr_body)
    check->>check: verify_spec(spec, base, head)
    check->>check: run test at HEAD
    check->>check: git archive base → tmpdir
    check->>check: run test at base snapshot
    check-->>GitHub API: write GITHUB_OUTPUT (verdict, has_marker)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: sync workflow templates' accurately describes the main change—syncing workflow templates from a source repository.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/workflows-536a715df8b0

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

@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

🤖 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 @.github/scripts/runtime_ac_merge_guard.js:
- Around line 63-77: The fetchPullRequestLabels function currently only fetches
the first page of labels (per_page: 100) and does not handle pagination. Instead
of using the withRetry wrapper with a single call to
client.rest.issues.listLabelsOnIssue, use the github.paginate() method to
iterate through all pages of results. Replace the try block logic so that if
withRetry is provided, wrap the paginate call with it; otherwise call paginate
directly on the github client, ensuring all labels are retrieved regardless of
count.

In `@scripts/check_deliberate_break.py`:
- Around line 236-286: The function verify_spec calls _changed_assertions and
_archive_ref which internally use _git with check=True, raising
subprocess.CalledProcessError on git failures, but this exception is not caught
in either try block, causing the script to crash instead of returning a
structured JSON result. Add exception handlers for subprocess.CalledProcessError
in both the first try block (containing _changed_assertions) and the second try
block (containing _archive_ref), and return _json_result with VERDICT_BROKEN and
an appropriate reason like "git-command-failed" along with error details from
the caught exception to maintain the contract that verify_spec always returns a
dict.
- Around line 190-197: Add the `--` separator before the ref argument in the git
commands to prevent flag injection. In the _archive_ref function, modify the git
archive command array to include `--` before the `base` parameter. Apply the
same defensive pattern to the _git function calls within _changed_assertions
function where refs are passed as arguments to prevent malicious or malformed
refs (starting with `-`) from being misinterpreted as flags by git.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 7a379d5e-7579-4295-85b7-f1c7eff4b32b

📥 Commits

Reviewing files that changed from the base of the PR and between ca10cfc and fc3b451.

📒 Files selected for processing (8)
  • .github/scripts/gate_summary.py
  • .github/scripts/runtime_ac_merge_guard.js
  • .github/workflows/agents-73-codex-belt-conveyor.yml
  • .github/workflows/agents-81-gate-followups.yml
  • .github/workflows/agents-guard.yml
  • AGENTS.md
  • CLAUDE.md
  • scripts/check_deliberate_break.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • stranske/Workflows (auto-detected)
  • stranske/Template (auto-detected)
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Python CI / python 3.13
  • GitHub Check: Python CI / python 3.12
🧰 Additional context used
📓 Path-based instructions (8)
{CLAUDE.md,AGENTS.md}

📄 CodeRabbit inference engine (CLAUDE.md)

Keep CLAUDE.md materially aligned with AGENTS.md; differences should only be agent-specific execution notes, not different repository rules

Files:

  • CLAUDE.md
  • AGENTS.md
{pyproject.toml,.github/workflows/*.{yml,yaml}}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

{pyproject.toml,.github/workflows/*.{yml,yaml}}: Ensure coverage thresholds in pyproject.toml ([tool.coverage.report] fail_under) match the coverage-min settings in CI workflow files to avoid threshold conflicts
The Manager-Database repository has a coverage threshold of 75%

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/agents-81-gate-followups.yml
  • .github/workflows/agents-73-codex-belt-conveyor.yml
.github/workflows/*.{yml,yaml}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

When a GitHub Actions workflow fails with startup_failure, check for invalid YAML syntax, top-level permissions: blocks on workflow_call reusable workflows (which conflicts with caller permissions), invalid permission scopes, or circular workflow references

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/agents-81-gate-followups.yml
  • .github/workflows/agents-73-codex-belt-conveyor.yml
.github/workflows/**/*.yml

📄 CodeRabbit inference engine (AGENTS.md)

Reference reusable workflows with @main unless intentionally pinning to exact commit SHA for a controlled reason

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/agents-81-gate-followups.yml
  • .github/workflows/agents-73-codex-belt-conveyor.yml
.github/workflows/agents-*.yml

📄 CodeRabbit inference engine (AGENTS.md)

Do not edit agents-*.yml files locally; fix issues in stranske/Workflows instead

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/agents-81-gate-followups.yml
  • .github/workflows/agents-73-codex-belt-conveyor.yml
.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

Reference reusable workflows with @main by default unless intentionally pinning to an exact commit SHA for a controlled reason

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/agents-81-gate-followups.yml
  • .github/workflows/agents-73-codex-belt-conveyor.yml
{.github/workflows/agents-*.yml,.github/workflows/autofix.yml,.github/codex/**,scripts/**,docs/**}

📄 CodeRabbit inference engine (CLAUDE.md)

Edit agents-*.yml, autofix.yml, .github/codex/ prompts, and synced scripts/docs in stranske/Workflows source-of-truth repository, not in the consumer repo

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/agents-81-gate-followups.yml
  • .github/workflows/agents-73-codex-belt-conveyor.yml
  • scripts/check_deliberate_break.py
**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

In the Manager-Database repository, use Prefect 2.x and import schedules from prefect.client.schemas.schedules

Files:

  • scripts/check_deliberate_break.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:22.568Z
Learning: Evaluate claims, designs, and instructions on merits before agreeing; when something is wrong, weaker than an alternative, or missing, say so plainly and lead with the strongest objection; provide confidence levels and flag uncertainties
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:22.568Z
Learning: Keep workflow logic in `stranske/Workflows` repository; consumer repo should only carry repo-specific configuration unless explicitly documented as an exception
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:22.568Z
Learning: Follow this order for infrastructure work source of truth: (1) stranske/Workflows root docs, (2) INTEGRATION_GUIDE.md and CONSUMER_REPO_MAINTENANCE.md, (3) consumer-repo/ templates, (4) local repo-specific files
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:22.568Z
Learning: Do not edit synced scripts and docs locally; fix issues in `stranske/Workflows` instead and update sync manifest if needed
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:22.568Z
Learning: Before editing local workflow infrastructure, determine whether the change belongs in `stranske/Workflows` instead; changes affecting reusable workflows, agent prompts, keepalive/autofix/verifier behavior, or synced files should be made in Workflows first
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:22.568Z
Learning: Keep AGENTS.md materially aligned with CLAUDE.md; differences should only be agent-specific execution notes, not different repository rules
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:32.561Z
Learning: Evaluate claims, designs, and instructions on merits before agreeing; provide calibrated dissent with confidence levels and conditions that would change your mind
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:32.561Z
Learning: Keep workflow logic in `stranske/Workflows` repository; consumer repo should only carry repo-specific configuration unless explicitly documented as an exception
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:32.561Z
Learning: For infrastructure work, follow source-of-truth order: (1) stranske/Workflows root docs, (2) INTEGRATION_GUIDE.md and CONSUMER_REPO_MAINTENANCE.md, (3) consumer-repo templates in Workflows, (4) local repo-specific files
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:32.561Z
Learning: Before editing local workflow infrastructure, evaluate whether the work belongs in `stranske/Workflows` instead, especially for reusable workflows, agent prompts, routing, keepalive/autofix/verifier behavior, and synced files
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:32.561Z
Learning: When making changes that belong in `stranske/Workflows`, make the source-of-truth change first, update the sync manifest if needed, then sync or manually align the consumer repo
Learnt from: CR
Repo: stranske/Collab-Admin

Timestamp: 2026-06-20T01:47:32.561Z
Learning: Use GitNexus opportunistically for cross-repo search, workflow/template drift checks, and ownership questions; treat `.gitnexus/` as local derived cache (do not commit or require in CI)
🪛 ast-grep (0.43.0)
scripts/check_deliberate_break.py

[info] 329-329: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_json_result(VERDICT_SKIPPED, reason="no deliberate-break marker"))
Note: Security best practice.

(use-jsonify)


[info] 341-341: use jsonify instead of json.dumps for JSON output
Context: json.dumps(result, sort_keys=True)
Note: Security best practice.

(use-jsonify)


[error] 143-150: Use of unsanitized data to create processes
Context: subprocess.run(
list(command),
cwd=cwd,
text=True,
capture_output=True,
env=env,
timeout=timeout,
)
Note: [CWE-78].

(os-system-unsanitized-data)


[error] 143-150: Command coming from incoming request
Context: subprocess.run(
list(command),
cwd=cwd,
text=True,
capture_output=True,
env=env,
timeout=timeout,
)
Note: [CWE-20].

(subprocess-from-request)


[error] 159-166: Command coming from incoming request
Context: subprocess.run(
["git", *args],
cwd=cwd,
check=True,
text=True,
capture_output=True,
timeout=timeout,
)
Note: [CWE-20].

(subprocess-from-request)


[error] 190-196: Command coming from incoming request
Context: subprocess.run(
["git", "archive", "--format=tar", base],
cwd=cwd,
check=True,
capture_output=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)
Note: [CWE-20].

(subprocess-from-request)

🔀 Multi-repo context stranske/Workflows, stranske/Template

Based on my comprehensive exploration of both repositories, here are the key cross-repository findings:

Linked repositories findings

stranske/Workflows (Source Repository)

New Public APIs and Exports:

[::stranske/Workflows::]

  • runtime_ac_merge_guard.js exports:
    • Constant: RUNTIME_AC_REQUIRED_LABELS = {'runtime-ac', 'runtime-verification', 'acceptance-criteria', 'verification-spec', 'verification-plan', 'ac-checks', 'runtime-checks'}
    • Function: assertRuntimeAcMergeAllowed({ github, core, owner, repo, prNumber, labels, withRetry, source }) — blocks merge and throws error with code: 'runtime_ac_merge_blocked' if PR has any required labels

[::stranske/Workflows::]

  • scripts/check_deliberate_break.py exports:
    • CLI entry via main(argv) with argparse; accepts --base, --head, --pr-body-file, --pr-body-env, --no-tamper-check
    • Function: verify_spec(spec, *, base, head="HEAD", cwd=None, enforce_tamper=True) — returns dict with verdict (VERDICT_PASS, VERDICT_HOLLOW, VERDICT_BROKEN, VERDICT_SKIPPED)
    • Function: parse_deliberate_break_spec(markdown) — extracts spec from PR body "Acceptance Criteria" section

[::stranske/Workflows::]

  • .github/scripts/gate_summary.py updated:
    • SummaryContext dataclass now includes test_quality_result: str = "skipped" (sourced from TEST_QUALITY_RESULT env var)
    • _append_job_table() now takes test_quality_result parameter and adds row to results table
    • summarize() now checks test_quality_result: if "cancelled" → state becomes "pending", if not "success" or "skipped" → state becomes "failure"

Integration in Source Workflows:

[::stranske/Workflows::]

  • .github/workflows/agents-73-codex-belt-conveyor.yml — calls assertRuntimeAcMergeAllowed() before squash merge with source: 'agents-73-codex-belt-conveyor'
  • .github/workflows/reusable-70-orchestrator-main.yml — calls assertRuntimeAcMergeAllowed() in automerge sweep with source: 'reusable-70-orchestrator-main automerge sweep'
  • .github/workflows/maint-71-merge-sync-prs.yml — calls assertRuntimeAcMergeAllowed() when merging sync PRs
  • .github/workflows/pr-00-gate.yml — invokes python scripts/check_deliberate_break.py --base "refs/remotes/upstream/${{ github.event.pull_request.base.ref }}" --head HEAD and passes TEST_QUALITY_RESULT env var to gate_summary.py

[::stranske/Workflows::]

  • Test Coverage: tests/scripts/test_check_deliberate_break.py validates deliberate-break verdict logic; tests/workflows/test_codex_belt_pipeline.py tests runtime AC guard integration
  • Documentation: docs/LABELS.md defines all runtime-ac label variants; Workflows AGENTS.md/CLAUDE.md now include "Working Stance — Critical Evaluator" guidance

stranske/Template (Consumer/Target Repository)

Current State & Compatibility Issues:

[::stranske/Template::]

  • CRITICAL — Missing Files: Template currently does NOT contain:
    • .github/scripts/runtime_ac_merge_guard.js (will be added by this sync)
    • scripts/check_deliberate_break.py (will be added by this sync)

[::stranske/Template::]

  • API Mismatch in gate_summary.py: Template's current .github/scripts/gate_summary.py does NOT have test_quality_result field in SummaryContext:
    • Current version: @dataclass(slots=True) class SummaryContext with fields up to docs_guard_result: str = "success" (missing test_quality_result)
    • After sync: Will receive updated version with test_quality_result: str = "skipped" field
    • Backwards Compatible: Default value of "skipped" means gate behavior unchanged if TEST_QUALITY_RESULT env var not set

[::stranske/Template::]

  • Workflows to be Updated: .github/workflows/agents-81-gate-followups.yml and .github/workflows/agents-73-codex-belt-conveyor.yml will be updated to import and call assertRuntimeAcMergeAllowed():
    • Both will require .github/scripts/github-api-with-retry.js to be available (Template has this)
    • Both will add sparse-checkout path for runtime_ac_merge_guard.js
    • Function call requires PR context (number, owner, repo) and optional labels array

[::stranske/Template::]

  • No TEST_QUALITY_RESULT currently set: Template's pr-00-gate.yml does not currently pass TEST_QUALITY_RESULT to gate_summary.py, so new field will always default to "skipped" after sync — no gate behavior change unless workflow explicitly sets this env var

Documentation Sync:

[::stranske/Template::]

  • docs/LABELS.md already documents runtime-ac labels; will be kept as-is
  • AGENTS.md and CLAUDE.md will be updated to include "Working Stance — Critical Evaluator" guidance matching the source repository

Summary of Risk:

The sync is backwards compatible for the gate_summary.py API change because test_quality_result defaults to "skipped". The main integration point is the addition of runtime AC merge guard calls in agents-73-codex-belt-conveyor.yml and agents-81-gate-followups.yml — these require the new runtime_ac_merge_guard.js file to be available, which will be synced. No existing Template code depends on these new scripts yet, so there are no consumer breakages expected.

🔇 Additional comments (12)
AGENTS.md (1)

5-8: LGTM!

CLAUDE.md (1)

5-8: LGTM!

.github/scripts/gate_summary.py (1)

24-24: LGTM!

Also applies to: 242-257, 270-273, 341-349, 387-400, 428-428, 442-442

scripts/check_deliberate_break.py (6)

1-34: LGTM!


37-56: LGTM!


58-131: LGTM!


170-188: LGTM!


312-347: LGTM!


133-151: Verify command execution trust model is acceptable.

The command parameter comes from the PR body's deliberate-break: marker and is safely parsed via shlex.split() before execution. Confirm this trust boundary aligns with your security requirements:

  • PR authors already have code execution through test files, so arbitrary commands in this context are not a privilege escalation
  • shlex.split() safely tokenizes the command string without shell interpretation
  • subprocess.run(list(command)) without shell=True executes the command directly without shell metacharacter expansion
  • Runs in GitHub Actions CI sandbox, not production
.github/workflows/agents-73-codex-belt-conveyor.yml (1)

195-195: LGTM!

Also applies to: 444-444, 456-464

.github/workflows/agents-81-gate-followups.yml (1)

1736-1744: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Reuse pr.labels in the runtime guard call to avoid redundant API reads.

This loop already has pr.labels; passing them into assertRuntimeAcMergeAllowed removes an extra labels API call per candidate PR.

Suggested patch
                             await assertRuntimeAcMergeAllowed({
                               github,
                               core,
                               owner,
                               repo,
                               prNumber,
+                              labels: pr.labels || [],
                               withRetry,
                               source: 'agents-81-gate-followups guarded merge',
                             });
			> Likely an incorrect or invalid review comment.
.github/workflows/agents-guard.yml (1)

109-117: LGTM!

Also applies to: 178-186

Comment on lines +63 to +77
async function fetchPullRequestLabels({ github, owner, repo, prNumber, withRetry }) {
if (!github || !github.rest || !github.rest.issues) {
throw new Error('GitHub client is required to evaluate runtime AC merge labels.');
}
const call = (client = github) =>
client.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number: prNumber,
per_page: 100,
});

try {
const response = withRetry ? await withRetry(call) : await call();
return Array.isArray(response && response.data) ? response.data : [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine the runtime_ac_merge_guard.js file
find . -name "runtime_ac_merge_guard.js" -type f

Repository: stranske/Collab-Admin

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Read the actual implementation of fetchPullRequestLabels
cat -n ".github/scripts/runtime_ac_merge_guard.js"

Repository: stranske/Collab-Admin

Length of output: 4591


🏁 Script executed:

#!/bin/bash
# Check how GitHub client paginate is used in existing workflows
rg "\.paginate\(" --type js -A 3 -B 1

Repository: stranske/Workflows

Length of output: 44


🌐 Web query:

GitHub REST API listLabelsOnIssue pagination behavior per_page limit

💡 Result:

The GitHub REST API endpoint for listing labels on an issue (GET /repos/{owner}/{repo}/issues/{issue_number}/labels) supports pagination via the per_page query parameter [1]. The per_page parameter allows you to control the number of results returned per page, with a maximum limit of 100 [1]. By default, this parameter is set to 30 if not specified [1]. For more extensive data retrieval, you can navigate through the paginated results using the page parameter or by utilizing the link header provided in the API response [1][2][3]. Summary of parameters: - per_page: The number of results to return per page. The minimum value is 1, and the maximum is 100 [1][4]. The default is 30 [1]. - page: The page number of the results to fetch [1]. The default is 1 [1].

Citations:


🏁 Script executed:

#!/bin/bash
# Check how GitHub clients are used; look for pagination patterns
rg "github\." --type js -A 2 -B 1 | head -100

Repository: stranske/Workflows

Length of output: 3267


🏁 Script executed:

#!/bin/bash
# Search for any octokit/github imports or usage patterns
rg "require.*github|import.*github" --type js

Repository: stranske/Workflows

Length of output: 1816


🌐 Web query:

Octokit GitHub client paginate method JavaScript

💡 Result:

The paginate method in the Octokit GitHub client (provided by the @octokit/plugin-paginate-rest plugin) is designed to automatically handle multi-page API responses by fetching subsequent pages until the last page is reached [1][2]. Key aspects of the paginate method: Usage: The method accepts the same arguments as octokit.request, which includes the REST API route as the first argument and parameters as the second [3][1]. You can also pass one of the registered REST endpoint methods (e.g., octokit.rest.issues.listForRepo) as the first argument for better readability and typing [3][4]. Example: const issues = await octokit.paginate("GET /repos/{owner}/{repo}/issues", { owner: "octocat", repo: "hello-world", per_page: 100, }); Mapping and Optimization: You can pass an optional mapping function as the third argument to process or transform the data from each response [3][5]. This is useful for reducing memory usage by storing only the required fields [5][6]. The map function receives a done callback, which allows you to stop pagination prematurely if a certain condition is met [3][6]. Memory Efficiency: While octokit.paginate fetches all items into a single array, for more memory-efficient processing, you can use octokit.paginate.iterator to handle one page of results at a time using an async iterator [4][6]. GraphQL Pagination: Note that Octokit also provides support for GraphQL pagination via octokit.graphql.paginate, which follows a different pattern based on GraphQL cursors [4].

Citations:


Paginate PR label reads to keep the runtime AC guard fail-closed.

fetchPullRequestLabels only reads the first page of results (per_page: 100). The GitHub REST API for listing issue labels is paginated, and if a PR has more than 100 labels, requirement detection will be incomplete, causing the guard to make incorrect merge decisions. Switch to the Octokit paginate() method, which is available on the GitHub client provided by @actions/github.

Suggested patch
 async function fetchPullRequestLabels({ github, owner, repo, prNumber, withRetry }) {
   if (!github || !github.rest || !github.rest.issues) {
     throw new Error('GitHub client is required to evaluate runtime AC merge labels.');
   }
-  const call = (client = github) =>
-    client.rest.issues.listLabelsOnIssue({
+  const call = async (client = github) => {
+    if (typeof client.paginate === 'function') {
+      return client.paginate(client.rest.issues.listLabelsOnIssue, {
+        owner,
+        repo,
+        issue_number: prNumber,
+        per_page: 100,
+      });
+    }
+    const response = await client.rest.issues.listLabelsOnIssue({
       owner,
       repo,
       issue_number: prNumber,
       per_page: 100,
-    });
+    });
+    return Array.isArray(response?.data) ? response.data : [];
+  };
 
   try {
-    const response = withRetry ? await withRetry(call) : await call();
-    return Array.isArray(response && response.data) ? response.data : [];
+    const labels = withRetry ? await withRetry(call) : await call();
+    return Array.isArray(labels) ? labels : [];
   } catch (error) {
🤖 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 @.github/scripts/runtime_ac_merge_guard.js around lines 63 - 77, The
fetchPullRequestLabels function currently only fetches the first page of labels
(per_page: 100) and does not handle pagination. Instead of using the withRetry
wrapper with a single call to client.rest.issues.listLabelsOnIssue, use the
github.paginate() method to iterate through all pages of results. Replace the
try block logic so that if withRetry is provided, wrap the paginate call with
it; otherwise call paginate directly on the github client, ensuring all labels
are retrieved regardless of count.

Comment on lines +190 to +197
def _archive_ref(base: str, target: Path, cwd: Path) -> None:
archive = subprocess.run(
["git", "archive", "--format=tar", base],
cwd=cwd,
check=True,
capture_output=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add -- before ref argument to prevent flag injection.

If base starts with - (e.g., a malicious or malformed ref), git could interpret it as a flag. While the base parameter comes from CLI args with a sensible default, using -- is a defensive best practice.

The same pattern applies to _git calls in _changed_assertions (lines 180, 183-186).

🛡️ Proposed fix
 def _archive_ref(base: str, target: Path, cwd: Path) -> None:
     archive = subprocess.run(
-        ["git", "archive", "--format=tar", base],
+        ["git", "archive", "--format=tar", "--", base],
         cwd=cwd,
         check=True,
         capture_output=True,
         timeout=DEFAULT_TIMEOUT_SECONDS,
     )

And similarly for _changed_assertions:

-    status = _git(["diff", "--name-status", f"{base}...{head}", "--", test_file], cwd)
+    status = _git(["diff", "--name-status", "--", f"{base}...{head}", "--", test_file], cwd)
     completed = _git(
-        ["diff", "--no-ext-diff", "--unified=0", f"{base}...{head}", "--", test_file],
+        ["diff", "--no-ext-diff", "--unified=0", "--", f"{base}...{head}", "--", test_file],
         cwd,
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _archive_ref(base: str, target: Path, cwd: Path) -> None:
archive = subprocess.run(
["git", "archive", "--format=tar", base],
cwd=cwd,
check=True,
capture_output=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)
def _archive_ref(base: str, target: Path, cwd: Path) -> None:
archive = subprocess.run(
["git", "archive", "--format=tar", "--", base],
cwd=cwd,
check=True,
capture_output=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)
🧰 Tools
🪛 ast-grep (0.43.0)

[error] 190-196: Command coming from incoming request
Context: subprocess.run(
["git", "archive", "--format=tar", base],
cwd=cwd,
check=True,
capture_output=True,
timeout=DEFAULT_TIMEOUT_SECONDS,
)
Note: [CWE-20].

(subprocess-from-request)

🤖 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/check_deliberate_break.py` around lines 190 - 197, Add the `--`
separator before the ref argument in the git commands to prevent flag injection.
In the _archive_ref function, modify the git archive command array to include
`--` before the `base` parameter. Apply the same defensive pattern to the _git
function calls within _changed_assertions function where refs are passed as
arguments to prevent malicious or malformed refs (starting with `-`) from being
misinterpreted as flags by git.

Comment on lines +236 to +286
try:
if enforce_tamper:
tampered = _changed_assertions(base, head, spec.test_file, repo)
if tampered:
return _json_result(
VERDICT_BROKEN,
reason="test-assertion-tamper",
test_file=spec.test_file,
changed_assertions=tampered,
)

head_run = _run(spec.command, repo)
except subprocess.TimeoutExpired as exc:
return _json_result(
VERDICT_BROKEN,
reason="command-timeout",
command=list(exc.cmd) if isinstance(exc.cmd, (tuple, list)) else str(exc.cmd),
timeout=exc.timeout,
)

if head_run.returncode != 0:
return _json_result(
VERDICT_BROKEN,
reason="head-test-failed",
test_id=spec.test_id,
command=list(spec.command),
stdout=head_run.stdout,
stderr=head_run.stderr,
)

try:
with tempfile.TemporaryDirectory(prefix="deliberate-break-base-") as tmp:
base_dir = Path(tmp)
_archive_ref(base, base_dir, repo)
base_test = base_dir / spec.test_file
base_test.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(test_path, base_test)
base_run = _run(spec.command, base_dir)
except subprocess.TimeoutExpired as exc:
return _json_result(
VERDICT_BROKEN,
reason="command-timeout",
command=list(exc.cmd) if isinstance(exc.cmd, (tuple, list)) else str(exc.cmd),
timeout=exc.timeout,
)
except ValueError as exc:
return _json_result(
VERDICT_BROKEN,
reason="archive-extract-failed",
detail=str(exc),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unhandled CalledProcessError will crash instead of returning JSON.

The _git calls (line 238 via _changed_assertions, line 269 via _archive_ref) use check=True, raising subprocess.CalledProcessError on non-zero exit. This exception is not caught, so if git fails (bad ref, corrupt repo, network issues), the script crashes with a traceback instead of returning a structured VERDICT_BROKEN result.

This breaks the contract that verify_spec always returns a dict, which downstream gate_summary.py may depend on.

🐛 Proposed fix
     try:
         if enforce_tamper:
             tampered = _changed_assertions(base, head, spec.test_file, repo)
             if tampered:
                 return _json_result(
                     VERDICT_BROKEN,
                     reason="test-assertion-tamper",
                     test_file=spec.test_file,
                     changed_assertions=tampered,
                 )
 
         head_run = _run(spec.command, repo)
     except subprocess.TimeoutExpired as exc:
         return _json_result(
             VERDICT_BROKEN,
             reason="command-timeout",
             command=list(exc.cmd) if isinstance(exc.cmd, (tuple, list)) else str(exc.cmd),
             timeout=exc.timeout,
         )
+    except subprocess.CalledProcessError as exc:
+        return _json_result(
+            VERDICT_BROKEN,
+            reason="git-command-failed",
+            command=exc.cmd,
+            returncode=exc.returncode,
+            stderr=exc.stderr,
+        )

Similarly for the second try block (lines 266-286):

     except ValueError as exc:
         return _json_result(
             VERDICT_BROKEN,
             reason="archive-extract-failed",
             detail=str(exc),
         )
+    except subprocess.CalledProcessError as exc:
+        return _json_result(
+            VERDICT_BROKEN,
+            reason="git-command-failed",
+            command=exc.cmd,
+            returncode=exc.returncode,
+            stderr=exc.stderr,
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
if enforce_tamper:
tampered = _changed_assertions(base, head, spec.test_file, repo)
if tampered:
return _json_result(
VERDICT_BROKEN,
reason="test-assertion-tamper",
test_file=spec.test_file,
changed_assertions=tampered,
)
head_run = _run(spec.command, repo)
except subprocess.TimeoutExpired as exc:
return _json_result(
VERDICT_BROKEN,
reason="command-timeout",
command=list(exc.cmd) if isinstance(exc.cmd, (tuple, list)) else str(exc.cmd),
timeout=exc.timeout,
)
if head_run.returncode != 0:
return _json_result(
VERDICT_BROKEN,
reason="head-test-failed",
test_id=spec.test_id,
command=list(spec.command),
stdout=head_run.stdout,
stderr=head_run.stderr,
)
try:
with tempfile.TemporaryDirectory(prefix="deliberate-break-base-") as tmp:
base_dir = Path(tmp)
_archive_ref(base, base_dir, repo)
base_test = base_dir / spec.test_file
base_test.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(test_path, base_test)
base_run = _run(spec.command, base_dir)
except subprocess.TimeoutExpired as exc:
return _json_result(
VERDICT_BROKEN,
reason="command-timeout",
command=list(exc.cmd) if isinstance(exc.cmd, (tuple, list)) else str(exc.cmd),
timeout=exc.timeout,
)
except ValueError as exc:
return _json_result(
VERDICT_BROKEN,
reason="archive-extract-failed",
detail=str(exc),
)
try:
if enforce_tamper:
tampered = _changed_assertions(base, head, spec.test_file, repo)
if tampered:
return _json_result(
VERDICT_BROKEN,
reason="test-assertion-tamper",
test_file=spec.test_file,
changed_assertions=tampered,
)
head_run = _run(spec.command, repo)
except subprocess.TimeoutExpired as exc:
return _json_result(
VERDICT_BROKEN,
reason="command-timeout",
command=list(exc.cmd) if isinstance(exc.cmd, (tuple, list)) else str(exc.cmd),
timeout=exc.timeout,
)
except subprocess.CalledProcessError as exc:
return _json_result(
VERDICT_BROKEN,
reason="git-command-failed",
command=exc.cmd,
returncode=exc.returncode,
stderr=exc.stderr,
)
if head_run.returncode != 0:
return _json_result(
VERDICT_BROKEN,
reason="head-test-failed",
test_id=spec.test_id,
command=list(spec.command),
stdout=head_run.stdout,
stderr=head_run.stderr,
)
try:
with tempfile.TemporaryDirectory(prefix="deliberate-break-base-") as tmp:
base_dir = Path(tmp)
_archive_ref(base, base_dir, repo)
base_test = base_dir / spec.test_file
base_test.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(test_path, base_test)
base_run = _run(spec.command, base_dir)
except subprocess.TimeoutExpired as exc:
return _json_result(
VERDICT_BROKEN,
reason="command-timeout",
command=list(exc.cmd) if isinstance(exc.cmd, (tuple, list)) else str(exc.cmd),
timeout=exc.timeout,
)
except ValueError as exc:
return _json_result(
VERDICT_BROKEN,
reason="archive-extract-failed",
detail=str(exc),
)
except subprocess.CalledProcessError as exc:
return _json_result(
VERDICT_BROKEN,
reason="git-command-failed",
command=exc.cmd,
returncode=exc.returncode,
stderr=exc.stderr,
)
🤖 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/check_deliberate_break.py` around lines 236 - 286, The function
verify_spec calls _changed_assertions and _archive_ref which internally use _git
with check=True, raising subprocess.CalledProcessError on git failures, but this
exception is not caught in either try block, causing the script to crash instead
of returning a structured JSON result. Add exception handlers for
subprocess.CalledProcessError in both the first try block (containing
_changed_assertions) and the second try block (containing _archive_ref), and
return _json_result with VERDICT_BROKEN and an appropriate reason like
"git-command-failed" along with error details from the caught exception to
maintain the contract that verify_spec always returns a dict.

@stranske-keepalive
stranske-keepalive Bot merged commit 79783c6 into main Jun 20, 2026
119 of 125 checks passed
@stranske-keepalive
stranske-keepalive Bot deleted the sync/workflows-536a715df8b0 branch June 20, 2026 01:56
@coderabbitai coderabbitai Bot mentioned this pull request Jun 26, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated Automated sync from Workflows sync Automated sync from Workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant