Skip to content

chore: sync workflow templates - #762

Merged
agents-workflows-bot[bot] merged 1 commit into
mainfrom
sync/workflows-97e42ef43aa5
Jun 21, 2026
Merged

chore: sync workflow templates#762
agents-workflows-bot[bot] merged 1 commit into
mainfrom
sync/workflows-97e42ef43aa5

Conversation

@stranske

@stranske stranske commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Sync Summary

Files Updated

  • agents-71-codex-belt-dispatcher.yml: Codex belt dispatcher - selects issues and creates agent branches for work
  • agents-72-codex-belt-worker.yml: Codex belt worker - executes agent on issues with full prompt and context
  • agents-72-codex-belt-worker-dispatch.yml: Codex belt worker dispatch wrapper - allows workflow_dispatch for the worker
  • check_agents_md_freshness.py: Warns when the generated Orchestrator AGENTS.md playbook section cites stale repo paths or commands
  • orchestrator_skill.py: Validates and resolves exported Orchestrator skill context for remote Codex lanes
  • runner_lib/ (1 files): Shared runner prompt assembly, output parsing, and dispatch debounce helpers
  • agent_delegation_policy.js: Agent delegation policy - system-driven routing for agent:auto label
  • keepalive_loop.js: Core keepalive loop logic
  • verifier_verdict_json.py: Extracts structured post-merge verifier verdict JSON without trusting diff text

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: 2baf871360a6ebf83c2b24dd7eafd94d7f262733
Template hash: 97e42ef43aa5
Sync branch: sync/workflows-97e42ef43aa5
Consumer repo: stranske/Counter_Risk
Manifest: .github/sync-manifest.yml

Summary by CodeRabbit

  • New Features

    • Added Orchestrator skill configuration management system with validation and materialization.
    • Added AGENTS.md validation tool to ensure referenced commands and paths exist.
    • Added verifier verdict extraction that detects tampering in Markdown output.
  • Enhancements

    • Improved agent stalling detection with lower thresholds for faster agent switching.
    • Implemented strict iteration budgets per pull request for better resource control.
    • Extended workflows to support Orchestrator skill overrides via inputs.

Automated sync from stranske/Workflows
Template hash: 97e42ef43aa5

Changes synced from sync-manifest.yml
@stranske stranske added sync Automated sync from Workflows automated Automated sync from Workflows labels Jun 21, 2026
@stranske
stranske temporarily deployed to agent-standard June 21, 2026 06:34 — with GitHub Actions Inactive
@stranske
stranske temporarily deployed to agent-standard June 21, 2026 06:34 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR tightens agent stall/effectiveness detection and replaces the keepalive soft-cap with a hard round budget. It introduces an orchestrator skill checkout system—new Python scripts, runner_lib materialization logic, and GitHub Actions workflow inputs—plus two new standalone CLI utilities: a tamper-resistant verifier verdict JSON extractor and an AGENTS.md managed-section freshness checker.

Changes

Agent Loop Control Tightening

Layer / File(s) Summary
Stall detection and effectiveness criteria
.github/scripts/agent_delegation_policy.js
detectStall default threshold reduced from 3 to 2; calculateEffectiveness and hasProgress now require tasks or commits with gate === 'pass'; commit-only activity without a green gate is no longer treated as progress.
Hard per-PR iteration budget
.github/scripts/keepalive_loop.js
Adds toPositiveInteger helper; normaliseConfig removes max_iterations default of 5; maxIterations resolves from config or state defaulting to 12; budget exhaustion unconditionally stops with round-budget-exhausted, removing extended-mode and ready-extended paths.

Orchestrator Skill Feature

Layer / File(s) Summary
Config schema, dataclasses, and validation
scripts/orchestrator_skill.py
Defines OrchestratorSkillConfigError, OrchestratorSkillCheckoutPlan, and OrchestratorSkillSnapshot; validates repo format, paths contents, forbidden markers, enabled coercion, and mutually exclusive pack vs inline repo/ref/paths.
Plan resolution, summary generation, and CLI
scripts/orchestrator_skill.py
resolve_orchestrator_skill_plan applies CLI overrides; load_orchestrator_skill produces a snapshot; build/write_orchestrator_skill_summary scans checkout dir and writes ORCHESTRATOR_SKILL.md; main() handles json/self-check formats and exits 2 on config error.
Sparse checkout materialization and prompt assembly
scripts/runner_lib/core.py
_materialize_single_checkout_plan() clones with sparse checkout and token auth; materialize_orchestrator_skill() routes between pack and single-plan paths; assemble_prompt() passes token, calls materialization, and appends ORCHESTRATOR_SKILL.md to the prompt; CLI gains --orchestrator-skill-pack, --orchestrator-skill-enabled, and --materialize-orchestrator-skill flags.
Workflow input propagation
.github/workflows/agents-71-codex-belt-dispatcher.yml, .github/workflows/agents-72-codex-belt-worker-dispatch.yml, .github/workflows/agents-72-codex-belt-worker.yml
Adds orchestrator_skill_pack and orchestrator_skill_enabled inputs to dispatcher and worker workflows; max_parallel in the dispatch job is hard-coded to 1.

New Utility Scripts

Layer / File(s) Summary
Verifier verdict parser and tamper detection
.github/scripts/verifier_verdict_json.py
Reads verifier Markdown, detects verdict spoofing inside diff/patch fenced blocks, extracts and normalizes verdicts from json blocks outside those regions, and writes a structured JSON verdict file with source and needs_attention fields.
AGENTS.md freshness checker
scripts/check_agents_md_freshness.py
Extracts the managed <!-- BEGIN orch-playbook --> section, validates all backtick-cited command and path refs for existence, deduplicates findings, and outputs JSON or GitHub Actions warning annotations with optional --strict non-zero exit.

Sequence Diagram(s)

sequenceDiagram
    participant Workflow as GitHub Actions Workflow
    participant core as runner_lib/core.py
    participant skill as orchestrator_skill.py
    participant git as git sparse-checkout
    participant Prompt as Generated Prompt

    Workflow->>core: assemble-prompt --materialize-orchestrator-skill
    core->>skill: resolve_orchestrator_skill_plan(pack_override, enabled_override)
    skill-->>core: OrchestratorSkillCheckoutPlan
    alt plan.pack is set
        core->>core: materialize_reference_packs(token)
    else direct repo plan
        core->>git: clone repo + sparse-checkout paths
        git-->>core: files written to .reference/
    end
    core->>skill: write_orchestrator_skill_summary(checkout_path)
    skill-->>core: .reference/ORCHESTRATOR_SKILL.md
    core->>Prompt: append "Orchestrator Skill Context" section
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'chore: sync workflow templates' is generic and does not clearly convey the substantial functional changes (agent delegation policy updates, keepalive loop hard budget enforcement, new orchestrator skill validation, verifier verdict extraction, etc.) that comprise the majority of the changeset. Consider a more specific title that highlights the primary functional changes, such as 'chore: sync workflows and add orchestrator skill validation' or mention the agent delegation policy/keepalive loop updates that represent the most significant changes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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-97e42ef43aa5

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

@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Workflow state fingerprint for Keepalive Loop Reporter. Do not edit.

@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Workflow state fingerprint for Agents Gate Followups. Do not edit.

@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: 7

🤖 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/verifier_verdict_json.py:
- Line 103: In the path handling logic where args.output is read, add explicit
error handling to catch the edge case where args.output exists but is a
directory rather than a file. Instead of silently treating a directory path as
empty output, raise an error or log a warning that clearly indicates the
configuration issue, so that CI pipeline failures are not masked by ambiguous
behavior. This ensures debugging in CI environments is more straightforward when
path configuration errors occur.

In @.github/workflows/agents-72-codex-belt-worker-dispatch.yml:
- Around line 77-80: The max_parallel field on line 77 is hard-coded to 1, which
silently overrides any max_parallel input value passed by callers and breaks
existing workflows expecting higher concurrency values. Replace the hard-coded
max_parallel: 1 with a reference to the input parameter using the pattern ${{
inputs.max_parallel }} to propagate the caller's concurrency preference. Note
that per coding guidelines, this fix should be applied in the stranske/Workflows
repository where the agents-*.yml workflow templates are maintained, not edited
locally in this consumer repository.

In `@scripts/check_agents_md_freshness.py`:
- Around line 62-63: The _path_exists function on line 63 and the similar code
on lines 71-73 have a path traversal vulnerability where absolute paths in the
ref and command parameters bypass repo scoping. When using the `/` operator with
Path objects, absolute paths ignore the left operand entirely, so a ref like
"/etc/hosts" would resolve outside the repo_root. Fix this by validating that
ref and command are relative paths and do not start with a forward slash, or by
using methods like resolve() and relative_to() to ensure the resulting path is
within the repo_root directory before checking if it exists.
- Around line 142-144: The args.agents_md path is being resolved relative to the
current working directory instead of relative to repo_root, which causes
incorrect file loading when the script is invoked outside the repo directory. In
the assignment where agents_md is set on line 143, change the resolution logic
so that args.agents_md is resolved relative to repo_root (which is already
resolved on line 142) instead of being resolved independently, ensuring the path
is correctly anchored to the repository root directory.

In `@scripts/orchestrator_skill.py`:
- Around line 82-85: The `_validate_repo` function currently only checks that a
forward slash exists and that the string does not start or end with one, but
this allows multiple slashes (e.g., org/team/repo) which violates the declared
owner/name format contract. Update the validation logic in `_validate_repo` to
ensure there is exactly one forward slash in the repo string by counting the
occurrences of "/" and rejecting any value that does not have exactly one slash
separator.
- Around line 221-242: The issue is that resolve_orchestrator_skill_plan()
returns early when enabled is False, before processing the pack_override
parameter, which causes the override to be ignored when the skill is disabled in
config. Restructure the logic to apply pack_override before checking the enabled
flag, or modify the early return condition to only return None when both enabled
is False AND pack_override is not provided. This ensures that CLI/workflow
overrides via pack_override take effect regardless of the enabled configuration
setting.

In `@scripts/runner_lib/core.py`:
- Around line 317-324: There is a path traversal vulnerability where
checkout_path and rel_path values can escape workspace_path. Before calling
shutil.rmtree on destination_root at line 319, add a canonical path containment
check to ensure destination_root is actually contained within workspace_path
after resolving any symlinks and relative paths. Similarly, before each copy
operation in the loop (lines 321-324), validate that both the src and dst paths
are within their intended boundaries by comparing their canonical resolved
paths. Use Path.resolve() to get canonical paths and ensure they start with the
workspace boundaries before proceeding with the potentially dangerous
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: ASSERTIVE

Plan: Pro Plus

Run ID: 63c259be-4831-4716-90eb-567ab5d1fd5e

📥 Commits

Reviewing files that changed from the base of the PR and between 8d55f2a and 00a5dc5.

📒 Files selected for processing (9)
  • .github/scripts/agent_delegation_policy.js
  • .github/scripts/keepalive_loop.js
  • .github/scripts/verifier_verdict_json.py
  • .github/workflows/agents-71-codex-belt-dispatcher.yml
  • .github/workflows/agents-72-codex-belt-worker-dispatch.yml
  • .github/workflows/agents-72-codex-belt-worker.yml
  • scripts/check_agents_md_freshness.py
  • scripts/orchestrator_skill.py
  • scripts/runner_lib/core.py
🔗 Linked repositories identified

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

  • stranske/Workflows (auto-detected)
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Python CI / python 3.12
  • GitHub Check: Update keepalive summary
🧰 Additional context used
📓 Path-based instructions (5)
.github/workflows/**/*.yml

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

.github/workflows/**/*.yml: Do not add top-level permissions: block on workflow_call reusable workflows - this conflicts with caller permissions
Avoid invalid YAML syntax, invalid permission scopes, and circular workflow references when creating GitHub Actions workflows

Files:

  • .github/workflows/agents-72-codex-belt-worker.yml
  • .github/workflows/agents-71-codex-belt-dispatcher.yml
  • .github/workflows/agents-72-codex-belt-worker-dispatch.yml
**/.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

First-party consumers should reference reusable workflows with @main. Match that unless you are intentionally pinning to an exact commit SHA for a controlled reason.

Files:

  • .github/workflows/agents-72-codex-belt-worker.yml
  • .github/workflows/agents-71-codex-belt-dispatcher.yml
  • .github/workflows/agents-72-codex-belt-worker-dispatch.yml
**/{.github/workflows/agents-*.yml,.github/workflows/autofix.yml,.github/codex/**,scripts/**,docs/**}

📄 CodeRabbit inference engine (CLAUDE.md)

Agent workflow files (agents-*.yml), autofix.yml, .github/codex/ prompts, and synced scripts/docs should be fixed in stranske/Workflows, not locally in the consumer repo.

Files:

  • .github/workflows/agents-72-codex-belt-worker.yml
  • .github/workflows/agents-71-codex-belt-dispatcher.yml
  • .github/workflows/agents-72-codex-belt-worker-dispatch.yml
  • scripts/check_agents_md_freshness.py
  • scripts/runner_lib/core.py
  • scripts/orchestrator_skill.py
.github/workflows/agents-*.yml

📄 CodeRabbit inference engine (AGENTS.md)

Agent workflow files (agents-*.yml) should be fixed in stranske/Workflows, not edited locally in the consumer repository

Files:

  • .github/workflows/agents-72-codex-belt-worker.yml
  • .github/workflows/agents-71-codex-belt-dispatcher.yml
  • .github/workflows/agents-72-codex-belt-worker-dispatch.yml
**/*.py

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

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

Files:

  • scripts/check_agents_md_freshness.py
  • scripts/runner_lib/core.py
  • scripts/orchestrator_skill.py
🪛 ast-grep (0.43.0)
scripts/check_agents_md_freshness.py

[info] 146-146: use jsonify instead of json.dumps for JSON output
Context: json.dumps({"findings": [finding.as_dict() for finding in findings]}, indent=2)
Note: Security best practice.

(use-jsonify)


[warning] 93-93: Do not make http calls without encryption
Context: "http://"
Note: [CWE-319].

(requests-http)

.github/scripts/verifier_verdict_json.py

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

(use-jsonify)

scripts/orchestrator_skill.py

[info] 394-394: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload, separators=(",", ":"))
Note: Security best practice.

(use-jsonify)

🔀 Multi-repo context stranske/Workflows

Based on my exploration of the stranske/Workflows repository, I can now provide findings on the cross-repository impact of this PR.

Linked repositories findings

stranske/Workflows (Template Source)

This PR synchronizes template files that are deployed to consumer repositories via the .github/sync-manifest.yml manifest and maint-68-sync-consumer-repos.yml workflow.

Key consumers identified via the sync manifest:

agent_delegation_policy.js [::stranske/Workflows::]

  • Called by: .github/workflows/agents-keepalive-loop.yml (and consumer-synced workflows)
  • Breaking change detected: detectStall() default threshold changed from 3 to 2 rounds
  • Impact: This makes agent:auto label delegation switch agents sooner when stall conditions are detected. Any keepalive workflow in consumer repos that calls decideNextAgent() with the stalled agent policy will now escalate 1 round earlier.
  • Test evidence: .github/scripts/__tests__/agent-delegation-policy.test.js (1704 files in repo include tests)

keepalive_loop.js [::stranske/Workflows::]

  • Called by: Workflows agents-keepalive-loop, agents-keepalive-loop-reporter.yml, and health-keepalive-e2e.yml
  • Breaking behavior change:
    • max_iterations handling: Previously, a missing config value would default to 5 during normalization. Now it defaults to 12 during evaluation (hard budget enforcement).
    • Dispatch behavior changed from "soft cap" (allowing productive agents to continue) to "hard per-PR budget"
    • Stop reason changed from ready-extended / productivity-based variants to round-budget-exhausted
  • Test evidence: Multiple test files found:
    • .github/scripts/__tests__/keepalive-loop.test.js
    • tests/keepalive-cascade.test.js
    • tests/workflows/fixtures/keepalive_loop/harness.js
  • Consumer impact: All synced consumer repos that use keepalive workflows will enforce a tighter iteration budget (12 instead of 5 or extended variants).

orchestrator_skill.py [::stranske/Workflows::]

  • New file (100% addition): Validates exported Orchestrator skill context configuration
  • Used by:
    • scripts/runner_lib/core.py - new materialize_orchestrator_skill() function
    • Workflows: Codex belt dispatcher/worker workflows now accept orchestrator_skill_pack and orchestrator_skill_enabled inputs
  • Test coverage: tests/scripts/test_orchestrator_skill.py includes validation tests
  • Consumer impact: Consumer repos receiving updated Codex belt worker/dispatcher workflows (agents-71, agents-72-worker, agents-72-worker-dispatch) will have new optional inputs for orchestrator skill. These have safe defaults (empty strings) so existing consumers won't break.

verifier_verdict_json.py [::stranske/Workflows::]

  • New file (116 lines): Extracts structured verifier verdicts from Markdown output with tampering detection
  • Integration: Part of agent/verifier post-merge pipeline
  • Security feature: Detects and fails if verdict markers appear inside diff/patch blocks (diff-tamper detection)
  • Consumer impact: Consumer repos synced with this file will gain post-merge verifier verdict extraction capability

Workflow input changes [::stranske/Workflows::]

Files synced to consumer repos:

  • agents-71-codex-belt-dispatcher.yml: Added orchestrator_skill_pack, orchestrator_skill_enabled inputs (optional, default: '')
  • agents-72-codex-belt-worker.yml: Added same inputs (optional, default: '')
  • agents-72-codex-belt-worker-dispatch.yml: Added same inputs + changed max_parallel from ${{ fromJSON(inputs.max_parallel) }} to hard-coded 1

Breaking change: agents-72-codex-belt-worker-dispatch.yml now hard-codes max_parallel to 1 instead of accepting it as a dispatch input. Any consumer workflow that was passing max_parallel > 1 to this worker dispatch will now be overridden.

Scripts with broad consumer impact [::stranske/Workflows::]

  • check_agents_md_freshness.py: Validates AGENTS.md playbook section (now synced to consumers)
  • reference_packs.py & runner_lib/core.py: Handle orchestrator skill materialization alongside reference packs

Test fixtures evidence: The sync-manifest.yml file explicitly lists all files being synced with template_sync: exact markers for orchestrator_skill.py and other sensitive files, indicating these are critical for the agent orchestration pipeline.

🔇 Additional comments (18)
.github/scripts/verifier_verdict_json.py (5)

1-10: LGTM!


12-21: LGTM!


24-32: LGTM!


35-64: LGTM!


67-94: LGTM!

.github/workflows/agents-71-codex-belt-dispatcher.yml (1)

23-35: LGTM!

Also applies to: 79-91

.github/workflows/agents-72-codex-belt-worker.yml (1)

51-63: LGTM!

.github/scripts/agent_delegation_policy.js (4)

87-87: LGTM!


227-233: LGTM!


263-263: LGTM!


280-282: LGTM!

.github/scripts/keepalive_loop.js (7)

145-162: LGTM!


1614-1614: LGTM!


2524-2526: LGTM!


2618-2622: LGTM!


2683-2685: LGTM!


2781-2791: LGTM!


2797-2799: LGTM!

parser.add_argument("--json", required=True, help="Destination verdict JSON path")
args = parser.parse_args()

output = Path(args.output).read_text(encoding="utf-8") if Path(args.output).is_file() else ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider explicit error handling for path edge cases.

If args.output points to a directory (exists but not a file), this silently treats it as empty output, which could mask configuration errors in CI pipelines. An explicit error might be more helpful for debugging.

-    output = Path(args.output).read_text(encoding="utf-8") if Path(args.output).is_file() else ""
+    output_path = Path(args.output)
+    if output_path.is_dir():
+        raise SystemExit(f"error: --output path is a directory: {args.output}")
+    output = output_path.read_text(encoding="utf-8") if output_path.is_file() else ""

That said, if the current behavior of returning an error verdict with missing-structured-json source is intentional for graceful degradation in cases where verifier output doesn't exist, feel free to dismiss this.

📝 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
output = Path(args.output).read_text(encoding="utf-8") if Path(args.output).is_file() else ""
output_path = Path(args.output)
if output_path.is_dir():
raise SystemExit(f"error: --output path is a directory: {args.output}")
output = output_path.read_text(encoding="utf-8") if output_path.is_file() else ""
🤖 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/verifier_verdict_json.py at line 103, In the path handling
logic where args.output is read, add explicit error handling to catch the edge
case where args.output exists but is a directory rather than a file. Instead of
silently treating a directory path as empty output, raise an error or log a
warning that clearly indicates the configuration issue, so that CI pipeline
failures are not masked by ambiguous behavior. This ensures debugging in CI
environments is more straightforward when path configuration errors occur.

Comment on lines +77 to +80
max_parallel: 1
keepalive: ${{ inputs.keepalive }}
orchestrator_skill_pack: ${{ inputs.orchestrator_skill_pack }}
orchestrator_skill_enabled: ${{ inputs.orchestrator_skill_enabled }}

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 | 🏗️ Heavy lift

max_parallel is now silently ignored by callers.

Line 77 hard-codes max_parallel: 1, overriding dispatch input-driven concurrency. This is a behavioral break for existing callers that pass values >1 and expect them to propagate to the worker.

Suggested fix (template-side)
-      max_parallel: 1
+      max_parallel: ${{ fromJSON(inputs.max_parallel) }}

As per coding guidelines, "**/{.github/workflows/agents-*.yml,...} and .github/workflows/agents-*.yml should be fixed in stranske/Workflows, not edited locally in the consumer repository."

📝 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
max_parallel: 1
keepalive: ${{ inputs.keepalive }}
orchestrator_skill_pack: ${{ inputs.orchestrator_skill_pack }}
orchestrator_skill_enabled: ${{ inputs.orchestrator_skill_enabled }}
max_parallel: ${{ fromJSON(inputs.max_parallel) }}
keepalive: ${{ inputs.keepalive }}
orchestrator_skill_pack: ${{ inputs.orchestrator_skill_pack }}
orchestrator_skill_enabled: ${{ inputs.orchestrator_skill_enabled }}
🤖 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/workflows/agents-72-codex-belt-worker-dispatch.yml around lines 77 -
80, The max_parallel field on line 77 is hard-coded to 1, which silently
overrides any max_parallel input value passed by callers and breaks existing
workflows expecting higher concurrency values. Replace the hard-coded
max_parallel: 1 with a reference to the input parameter using the pattern ${{
inputs.max_parallel }} to propagate the caller's concurrency preference. Note
that per coding guidelines, this fix should be applied in the stranske/Workflows
repository where the agents-*.yml workflow templates are maintained, not edited
locally in this consumer repository.

Sources: Coding guidelines, Linked repositories

Comment on lines +62 to +63
def _path_exists(repo_root: Path, ref: str) -> bool:
return (repo_root / ref).exists()

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

Absolute path refs are incorrectly treated as valid local references.

On Line 63 and Line 72, joining repo_root / ref (or repo_root / command) allows absolute paths to bypass repo scoping, so host paths like /etc/hosts can be marked as valid and hide stale AGENTS.md refs.

Suggested fix
 def _path_exists(repo_root: Path, ref: str) -> bool:
-    return (repo_root / ref).exists()
+    path = Path(ref)
+    if path.is_absolute():
+        return False
+    return (repo_root / path).exists()
@@
 def _command_exists(repo_root: Path, ref: str) -> bool:
@@
     command = parts[0]
     if command.startswith(("./", "../")) or "/" in command:
-        return (repo_root / command).exists()
+        command_path = Path(command)
+        if command_path.is_absolute():
+            return False
+        return (repo_root / command_path).exists()
     return shutil.which(command) is not None

Also applies to: 71-73

🤖 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_agents_md_freshness.py` around lines 62 - 63, The _path_exists
function on line 63 and the similar code on lines 71-73 have a path traversal
vulnerability where absolute paths in the ref and command parameters bypass repo
scoping. When using the `/` operator with Path objects, absolute paths ignore
the left operand entirely, so a ref like "/etc/hosts" would resolve outside the
repo_root. Fix this by validating that ref and command are relative paths and do
not start with a forward slash, or by using methods like resolve() and
relative_to() to ensure the resulting path is within the repo_root directory
before checking if it exists.

Comment on lines +142 to +144
repo_root = args.repo_root.resolve()
agents_md = args.agents_md.resolve() if args.agents_md else None
findings = check_agents_md(repo_root, agents_md)

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

--agents-md is resolved relative to CWD instead of --repo-root.

Line 143 resolves args.agents_md against the process working directory, which can load the wrong file when the CLI is invoked outside the repo root.

Suggested fix
     repo_root = args.repo_root.resolve()
-    agents_md = args.agents_md.resolve() if args.agents_md else None
+    if args.agents_md:
+        agents_md = (
+            args.agents_md
+            if args.agents_md.is_absolute()
+            else (repo_root / args.agents_md)
+        ).resolve()
+    else:
+        agents_md = None
     findings = check_agents_md(repo_root, agents_md)
🤖 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_agents_md_freshness.py` around lines 142 - 144, The
args.agents_md path is being resolved relative to the current working directory
instead of relative to repo_root, which causes incorrect file loading when the
script is invoked outside the repo directory. In the assignment where agents_md
is set on line 143, change the resolution logic so that args.agents_md is
resolved relative to repo_root (which is already resolved on line 142) instead
of being resolved independently, ensuring the path is correctly anchored to the
repository root directory.

Comment on lines +82 to +85
def _validate_repo(repo: str) -> str:
if "/" not in repo or repo.startswith("/") or repo.endswith("/"):
raise OrchestratorSkillConfigError("repo must use owner/name format")
return repo

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

repo validation is too permissive for the declared owner/name contract.

Current validation accepts values with multiple slashes (e.g., org/team/repo). This defers a config error to runtime checkout instead of failing fast during validation.

Proposed fix
 def _validate_repo(repo: str) -> str:
-    if "/" not in repo or repo.startswith("/") or repo.endswith("/"):
+    if repo.count("/") != 1 or repo.startswith("/") or repo.endswith("/"):
         raise OrchestratorSkillConfigError("repo must use owner/name format")
     return repo
📝 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 _validate_repo(repo: str) -> str:
if "/" not in repo or repo.startswith("/") or repo.endswith("/"):
raise OrchestratorSkillConfigError("repo must use owner/name format")
return repo
def _validate_repo(repo: str) -> str:
if repo.count("/") != 1 or repo.startswith("/") or repo.endswith("/"):
raise OrchestratorSkillConfigError("repo must use owner/name format")
return repo
🤖 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/orchestrator_skill.py` around lines 82 - 85, The `_validate_repo`
function currently only checks that a forward slash exists and that the string
does not start or end with one, but this allows multiple slashes (e.g.,
org/team/repo) which violates the declared owner/name format contract. Update
the validation logic in `_validate_repo` to ensure there is exactly one forward
slash in the repo string by counting the occurrences of "/" and rejecting any
value that does not have exactly one slash separator.

Comment on lines +221 to +242
if not enabled:
return None

if pack_override:
if plan is None:
plan = OrchestratorSkillCheckoutPlan(
repo="",
ref="",
paths=[],
checkout_path=DEFAULT_CHECKOUT_PATH,
pack=pack_override,
)
else:
plan = OrchestratorSkillCheckoutPlan(
repo=plan.repo,
ref=plan.ref,
paths=list(plan.paths),
checkout_path=plan.checkout_path,
pack=pack_override,
)

return plan

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

pack_override is dropped when config exists but is disabled.

resolve_orchestrator_skill_plan() returns early on enabled == False before applying pack_override. That contradicts the intended override precedence and prevents CLI/workflow override from taking effect unless enabled_override=true is also passed.

Proposed fix
-    if not enabled:
-        return None
-
     if pack_override:
         if plan is None:
             plan = OrchestratorSkillCheckoutPlan(
                 repo="",
                 ref="",
                 paths=[],
                 checkout_path=DEFAULT_CHECKOUT_PATH,
                 pack=pack_override,
             )
+            enabled = True
         else:
             plan = OrchestratorSkillCheckoutPlan(
                 repo=plan.repo,
                 ref=plan.ref,
                 paths=list(plan.paths),
                 checkout_path=plan.checkout_path,
                 pack=pack_override,
             )
 
+    if not enabled:
+        return None
+
     return plan
🤖 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/orchestrator_skill.py` around lines 221 - 242, The issue is that
resolve_orchestrator_skill_plan() returns early when enabled is False, before
processing the pack_override parameter, which causes the override to be ignored
when the skill is disabled in config. Restructure the logic to apply
pack_override before checking the enabled flag, or modify the early return
condition to only return None when both enabled is False AND pack_override is
not provided. This ensures that CLI/workflow overrides via pack_override take
effect regardless of the enabled configuration setting.

Comment on lines +317 to +324
destination_root = workspace_path / checkout_path
if destination_root.exists():
shutil.rmtree(destination_root)
destination_root.mkdir(parents=True, exist_ok=True)
for rel_path in paths:
src = clone_dir / rel_path
dst = destination_root / rel_path
if src.is_dir():

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

Constrain checkout and copied paths to stay inside the workspace.

Line 317 and Line 323 trust checkout_path/rel_path at the sink. A crafted value can escape workspace_path, and Line 319 could delete unintended directories before copy. Add canonical path containment checks before rmtree and before each copy target/source resolution.

Suggested fix
-        destination_root = workspace_path / checkout_path
+        destination_root = (workspace_path / checkout_path).resolve()
+        try:
+            destination_root.relative_to(workspace_path.resolve())
+        except ValueError as exc:
+            raise ValueError(
+                f"orchestrator checkout_path must stay under workspace: {checkout_path}"
+            ) from exc
         if destination_root.exists():
             shutil.rmtree(destination_root)
         destination_root.mkdir(parents=True, exist_ok=True)
         for rel_path in paths:
-            src = clone_dir / rel_path
-            dst = destination_root / rel_path
+            rel = Path(rel_path)
+            if rel.is_absolute() or ".." in rel.parts:
+                raise ValueError(f"invalid orchestrator path entry: {rel_path}")
+            src = (clone_dir / rel).resolve()
+            dst = (destination_root / rel).resolve()
+            try:
+                src.relative_to(clone_dir.resolve())
+                dst.relative_to(destination_root)
+            except ValueError as exc:
+                raise ValueError(f"path escapes checkout boundaries: {rel_path}") from exc
📝 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
destination_root = workspace_path / checkout_path
if destination_root.exists():
shutil.rmtree(destination_root)
destination_root.mkdir(parents=True, exist_ok=True)
for rel_path in paths:
src = clone_dir / rel_path
dst = destination_root / rel_path
if src.is_dir():
destination_root = (workspace_path / checkout_path).resolve()
try:
destination_root.relative_to(workspace_path.resolve())
except ValueError as exc:
raise ValueError(
f"orchestrator checkout_path must stay under workspace: {checkout_path}"
) from exc
if destination_root.exists():
shutil.rmtree(destination_root)
destination_root.mkdir(parents=True, exist_ok=True)
for rel_path in paths:
rel = Path(rel_path)
if rel.is_absolute() or ".." in rel.parts:
raise ValueError(f"invalid orchestrator path entry: {rel_path}")
src = (clone_dir / rel).resolve()
dst = (destination_root / rel).resolve()
try:
src.relative_to(clone_dir.resolve())
dst.relative_to(destination_root)
except ValueError as exc:
raise ValueError(f"path escapes checkout boundaries: {rel_path}") from exc
if src.is_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 `@scripts/runner_lib/core.py` around lines 317 - 324, There is a path traversal
vulnerability where checkout_path and rel_path values can escape workspace_path.
Before calling shutil.rmtree on destination_root at line 319, add a canonical
path containment check to ensure destination_root is actually contained within
workspace_path after resolving any symlinks and relative paths. Similarly,
before each copy operation in the loop (lines 321-324), validate that both the
src and dst paths are within their intended boundaries by comparing their
canonical resolved paths. Use Path.resolve() to get canonical paths and ensure
they start with the workspace boundaries before proceeding with the potentially
dangerous operations.

@agents-workflows-bot
agents-workflows-bot Bot merged commit 67e333c into main Jun 21, 2026
76 of 82 checks passed
@agents-workflows-bot
agents-workflows-bot Bot deleted the sync/workflows-97e42ef43aa5 branch June 21, 2026 07:09
@coderabbitai coderabbitai Bot mentioned this pull request Jun 22, 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