Skip to content

chore: sync workflow templates - #836

Merged
agents-workflows-bot[bot] merged 1 commit into
mainfrom
sync/workflows-7456dc211466
Jun 20, 2026
Merged

chore: sync workflow templates#836
agents-workflows-bot[bot] merged 1 commit into
mainfrom
sync/workflows-7456dc211466

Conversation

@stranske

@stranske stranske commented Jun 20, 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
  • 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

  • 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: 509eceb28eb49f48ca51d40b77841cf7acf701d6
Template hash: 7456dc211466
Sync branch: sync/workflows-7456dc211466
Consumer repo: stranske/Template
Manifest: .github/sync-manifest.yml

Summary by CodeRabbit

  • New Features

    • Added Orchestrator skill configuration system for enhanced agent context management.
  • Improvements

    • Enhanced agent delegation policy with increased stall detection sensitivity.
    • Improved iteration budget control with stricter positive integer validation.
    • Updated workflow inputs to support Orchestrator skill overrides for agent runs.

Automated sync from stranske/Workflows
Template hash: 7456dc211466

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

This PR tightens the agent delegation policy's stall detection (threshold lowered from 3 to 2, effectiveness now requires gate=pass alongside commits) and the keepalive loop's iteration budget (removes productive-agent extension, adds round-budget-exhausted stop, changes default cap to 12). It also introduces a full orchestrator skill system: a new config/validation/materialization Python module, runner library integration for sparse-clone checkout and prompt injection, and propagation of two new workflow inputs through the belt dispatcher and worker pipelines. A new verifier verdict parser script is added to extract tamper-resistant structured verdicts from Markdown output.

Changes

Agent Loop Control Tightening

Layer / File(s) Summary
Stall detection and effectiveness criteria
.github/scripts/agent_delegation_policy.js
Stall threshold default and call-site value lowered from 3 to 2; calculateEffectiveness changed from "commits OR tasks" to "tasks OR (commits AND gate=pass)"; detectStall progress logic applies the same gate condition.
Keepalive iteration budget and stop logic
.github/scripts/keepalive_loop.js
Adds toPositiveInteger() helper; defers max_iterations default to budget resolution (fallback 12); removes productive-agent gating so cap always triggers; adds round-budget-exhausted stop branch; removes ready-extended dispatch reason.

Orchestrator Skill Feature

Layer / File(s) Summary
Config contracts, validation, and parsing
scripts/orchestrator_skill.py
Defines constants, OrchestratorSkillConfigError, frozen dataclasses OrchestratorSkillCheckoutPlan and OrchestratorSkillSnapshot, config loading, field validators (forbidden markers, absolute/traversal paths, repo format), enabled-field coercion, and JSON parsing with pack/inline mutual exclusivity.
Plan resolution, snapshot loading, and summary output
scripts/orchestrator_skill.py
Implements plan resolution with config/override merging, load_orchestrator_skill snapshot builder, markdown summary generation over materialized files, .reference/ORCHESTRATOR_SKILL.md writer, CLI argument parsing, and main() with json/self-check modes.
Runner library: materialization and prompt assembly
scripts/runner_lib/core.py
Adds _load_orchestrator_skill_module(), _materialize_single_checkout_plan() (sparse-clone with token), and materialize_orchestrator_skill() (routes to pack or single-checkout, writes summary). Updates assemble_prompt() to call materialization, appends ORCHESTRATOR_SKILL.md to the prompt, adds _parse_optional_bool(), and wires three new CLI args.
Workflow inputs and forwarding
.github/workflows/agents-71-codex-belt-dispatcher.yml, .github/workflows/agents-72-codex-belt-worker*.yml
Adds orchestrator_skill_pack and orchestrator_skill_enabled inputs to dispatcher (workflow_call + workflow_dispatch), worker (workflow_call), and worker-dispatch (workflow_dispatch); hard-codes max_parallel: 1 in the dispatch wrapper and forwards both inputs to the reusable worker.

Verifier Verdict Parser

Layer / File(s) Summary
Verdict extraction, normalization, and CLI
.github/scripts/verifier_verdict_json.py
New script defining regexes, synonym normalization, diff/patch region neutralization, JSON block collection, build_verdict() (tamper detection then structured JSON scan), and main() reading Markdown and writing sorted verdict JSON.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as Belt Workflow
  participant assemble_prompt
  participant materialize_orchestrator_skill
  participant resolve_orchestrator_skill_plan as resolve_plan
  participant _materialize_single_checkout_plan as sparse_clone
  participant write_orchestrator_skill_summary as write_summary
  Workflow->>assemble_prompt: orchestrator_skill_pack, orchestrator_skill_enabled, token
  assemble_prompt->>materialize_orchestrator_skill: pack_override, enabled_override, token
  materialize_orchestrator_skill->>resolve_plan: workspace, overrides
  resolve_plan-->>materialize_orchestrator_skill: OrchestratorSkillCheckoutPlan | None
  alt plan.pack is set
    materialize_orchestrator_skill->>materialize_orchestrator_skill: materialize_reference_packs(pack)
  else inline checkout
    materialize_orchestrator_skill->>sparse_clone: repo, ref, paths, token
    sparse_clone-->>materialize_orchestrator_skill: checkout_path
  end
  materialize_orchestrator_skill->>write_summary: checkout_path, pack_name
  write_summary-->>materialize_orchestrator_skill: .reference/ORCHESTRATOR_SKILL.md
  materialize_orchestrator_skill-->>assemble_prompt: summary_path
  assemble_prompt->>assemble_prompt: append ORCHESTRATOR_SKILL.md to prompt
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.95% 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: synchronizing workflow files from the Workflows repository, which aligns with the PR's primary objective.
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-7456dc211466

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

@stranske-keepalive

stranske-keepalive Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Automated Status Summary

Head SHA: 7d2789d
Latest Runs: ⏳ pending — Gate
Required: core tests (3.12): ⏳ pending, core tests (3.13): ⏳ pending, docker smoke: ⏳ pending, gate: ⏳ pending

Workflow / Job Result Logs
(no jobs reported) ⏳ pending

Coverage Overview

  • Coverage history entries: 1

Coverage Trend

Metric Value
Current 100.00%
Baseline 0.00%
Delta +100.00%
Minimum 70.00%
Status ✅ Pass

Top Coverage Hotspots (lowest coverage)

File Coverage Missing
src/my_project/__init__.py 100.0% 0

Updated automatically; will refresh on subsequent CI/Docker completions.


Keepalive checklist

Scope

No scope information available

Tasks

  • No tasks defined

Acceptance criteria

  • No acceptance criteria defined

@stranske
stranske temporarily deployed to agent-high-privilege June 20, 2026 06:30 — with GitHub Actions Inactive

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

🤖 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 86: The "needs_attention" field assignment at line 86 incorrectly uses
bool() on candidate.get() which misinterprets string values like "false" as True
since any non-empty string is truthy in Python. Replace the bool() coercion with
explicit string comparison to check if the value equals "true"
(case-insensitive) or use proper JSON boolean coercion instead of relying on
Python's truthiness evaluation. This ensures that string values from JSON are
properly converted to their intended boolean representation.

In `@scripts/orchestrator_skill.py`:
- Around line 206-223: The issue is that when a config file has "enabled":
false, the parse_orchestrator_skill_config_text function returns plan=None, and
subsequently setting enabled_override=True only flips the enabled flag but
leaves plan=None, causing the function to still return None. To fix this, after
applying the enabled_override in the section where enabled = enabled_override is
set, add logic to also restore or create the plan when enabled_override=True and
plan is currently None. Check if pack_override is available and use it to create
a plan with the same structure as in the elif pack_override block, ensuring that
both enabled and plan are properly set when the override is applied.

In `@scripts/runner_lib/core.py`:
- Around line 944-945: The orchestrator_skill_pack field in the context
dictionary at line 944 does not normalize whitespace-only input from
args.orchestrator_skill_pack, causing whitespace strings to be treated as valid
pack overrides that later fail with "pack not found". Normalize the
args.orchestrator_skill_pack value by stripping whitespace before assigning it
to the orchestrator_skill_pack key, ensuring that whitespace-only strings are
converted to None just like other override values are parsed.
- Around line 321-329: The copy operations in the file copy loop for clone_dir
paths are vulnerable to symlink escape attacks because shutil.copytree and
shutil.copy2 can follow symlinks by default. Add a check for symlinks using
is_symlink() before processing each path in the loop, and reject or skip any
symlink paths to prevent malicious symlinks from copying content outside
clone_dir. Additionally, add follow_symlinks=False parameter to the shutil.copy2
call and ensure symlinks=False is set (or explicit handling) in the
shutil.copytree call to prevent symlink traversal during the copy 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: 43ef96ed-c17e-41c0-b65c-0ee51c08c0b5

📥 Commits

Reviewing files that changed from the base of the PR and between 0db007c and 939a8a3.

📒 Files selected for processing (8)
  • .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/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
🧰 Additional context used
📓 Path-based instructions (5)
.github/workflows/**/*.{yml,yaml}

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

.github/workflows/**/*.{yml,yaml}: Ensure coverage threshold settings in GitHub Actions workflow files for coverage-min match the [tool.coverage.report] fail_under setting in pyproject.toml, as the lower value will be the effective threshold
For startup_failure in GitHub Actions workflows with zero jobs, check for invalid YAML syntax, top-level permissions: blocks in workflow_call reusable workflows (which conflicts with caller permissions), invalid permission scopes, or circular workflow references

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)

Reference reusable workflows with @main unless 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/**/*.yml

📄 CodeRabbit inference engine (AGENTS.md)

First-party consumers should reference reusable workflows with @main unless 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

📄 CodeRabbit inference engine (AGENTS.md)

Agent workflows (agents-*.yml), autofix.yml, synced prompts in .github/codex/, and synced scripts/docs should be fixed in stranske/Workflows, not 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 using Prefect 2.x, import schedules from prefect.client.schemas.schedules rather than other locations

Files:

  • scripts/runner_lib/core.py
  • scripts/orchestrator_skill.py
🪛 ast-grep (0.43.0)
.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

Linked repositories findings

stranske/Workflows

Integration of orchestrator_skill.py with agent workflows:

  • [::stranske/Workflows::] scripts/runner_lib/core.py — imports and calls orchestrator_skill.resolve_orchestrator_skill_plan() and orchestrator_skill.write_orchestrator_skill_summary() within materialize_orchestrator_skill() function, which is used during prompt assembly.
  • [::stranske/Workflows::] .github/workflows/agents-71-codex-belt-dispatcher.yml — exposes orchestrator_skill_pack and orchestrator_skill_enabled inputs (lines 29-38) that are intended to override orchestrator skill context for downstream Codex runs.
  • [::stranske/Workflows::] .github/workflows/agents-72-codex-belt-worker.yml — accepts the same orchestrator_skill_pack and orchestrator_skill_enabled inputs, which are forwarded through prompt assembly.
  • [::stranske/Workflows::] .github/workflows/agents-72-codex-belt-worker-dispatch.yml — wrapper that passes orchestrator_skill_pack and orchestrator_skill_enabled to the worker workflow.
  • [::stranske/Workflows::] tests/scripts/test_runner_lib.py — test test_assemble_prompt_includes_orchestrator_skill_section_when_summary_exists confirms integration with prompt assembly when ORCHESTRATOR_SKILL.md is present.

Keepalive loop behavior changes affecting agent dispatch:

  • [::stranske/Workflows::] .github/scripts/keepalive_loop.js — contains toPositiveInteger() helper and updated max-iterations resolution logic. The shouldStopForMaxIterations condition now always triggers when iteration cap is reached, with explicit stop reason round-budget-exhausted (lines 2622-2685).
  • [::stranske/Workflows::] .github/workflows/agents-72-codex-belt-worker.yml:407-420 — evaluates keepalive_worker_gate when inputs.keepalive == true, directly integrating with keepalive loop state.
  • [::stranske/Workflows::] tests/keepalive-cascade.test.js, tests/keepalive-gate-failure-counter.test.js — have test coverage for keepalive loop behavior.

Agent delegation policy stall detection sensitivity:

  • [::stranske/Workflows::] .github/scripts/agent_delegation_policy.js — contains decideNextAgent() function that calls detectStall() and applies effectiveness calculation logic with threshold of 2 (per AI summary change from 3).
  • [::stranske/Workflows::] .github/scripts/__tests__/agent-delegation-policy.test.js — 12KB test file providing coverage for delegation logic.

Verifier verdict JSON parsing:

  • [::stranske/Workflows::] .github/scripts/verifier_verdict_json.py — new module synced in manifest that parses Markdown output to extract structured verdicts, preventing diff-text tampering.
  • [::stranske/Workflows::] tests/workflows/test_verifier_verdict_parsing.py — comprehensive test coverage including test_diff_block_with_inner_backticks_still_tamper() and verdict schema validation.

Sync manifest confirms all changed files are intentionally synced to consumers:

  • [::stranske/Workflows::] .github/sync-manifest.yml — documents each file in the scripts: section:
    • scripts/orchestrator_skill.py — "Validates and resolves exported Orchestrator skill context for remote Codex lanes"
    • .github/scripts/agent_delegation_policy.js — "Agent delegation policy - system-driven routing for agent:auto label"
    • .github/scripts/keepalive_loop.js — "Core keepalive loop logic"
    • .github/scripts/verifier_verdict_json.py — "Extracts structured post-merge verifier verdict JSON without trusting diff text"
    • .github/workflows/agents-71-codex-belt-dispatcher.yml — "Codex belt dispatcher - selects issues and creates agent branches for work"
    • .github/workflows/agents-72-codex-belt-worker.yml — "Codex belt worker - executes agent on issues with full prompt and context"
    • .github/workflows/agents-72-codex-belt-worker-dispatch.yml — "Codex belt worker dispatch wrapper - allows workflow_dispatch for the worker"

Cross-file dependencies:

  • [::stranske/Workflows::] scripts/runner_lib/core.py — calls _load_orchestrator_skill_module() which imports scripts.orchestrator_skill, establishing hard dependency. Failure to sync orchestrator_skill.py causes RuntimeError with message "scripts/orchestrator_skill.py was not synced".
  • [::stranske/Workflows::] reusable-codex-run.yml — invokes python -m scripts.runner_lib assemble-prompt with flags like --materialize-orchestrator-skill, which depends on orchestrator_skill module being present.
🔇 Additional comments (13)
.github/scripts/verifier_verdict_json.py (1)

12-85: LGTM!

Also applies to: 89-117

.github/scripts/agent_delegation_policy.js (3)

87-87: LGTM!

Also applies to: 263-263


227-233: LGTM!


281-282: LGTM!

.github/scripts/keepalive_loop.js (6)

145-162: LGTM!


1614-1614: LGTM!


2524-2526: LGTM!


2618-2622: LGTM!


2683-2685: LGTM!


2799-2799: 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/workflows/agents-72-codex-belt-worker-dispatch.yml (1)

45-57: LGTM!

Also applies to: 77-80

**candidate,
"verdict": verdict,
"source": "structured-json",
"needs_attention": bool(candidate.get("needs_attention", verdict != "pass")),

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

Coerce needs_attention explicitly instead of using bool() on arbitrary JSON values

At Line 86, bool(candidate.get("needs_attention", ...)) misinterprets string values like "false" as True. That can flip verifier routing/gating behavior.

Suggested fix
+def _coerce_optional_bool(value: object) -> bool | None:
+    if isinstance(value, bool):
+        return value
+    if isinstance(value, str):
+        normalized = value.strip().lower()
+        if normalized in {"true", "1", "yes", "on"}:
+            return True
+        if normalized in {"false", "0", "no", "off"}:
+            return False
+    return None
+
@@
-        return {
+        parsed_attention = _coerce_optional_bool(candidate.get("needs_attention"))
+        return {
             **candidate,
             "verdict": verdict,
             "source": "structured-json",
-            "needs_attention": bool(candidate.get("needs_attention", verdict != "pass")),
+            "needs_attention": parsed_attention if parsed_attention is not None else (verdict != "pass"),
         }
📝 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
"needs_attention": bool(candidate.get("needs_attention", verdict != "pass")),
def _coerce_optional_bool(value: object) -> bool | None:
if isinstance(value, bool):
return value
if isinstance(value, str):
normalized = value.strip().lower()
if normalized in {"true", "1", "yes", "on"}:
return True
if normalized in {"false", "0", "no", "off"}:
return False
return None
parsed_attention = _coerce_optional_bool(candidate.get("needs_attention"))
return {
**candidate,
"verdict": verdict,
"source": "structured-json",
"needs_attention": parsed_attention if parsed_attention is not None else (verdict != "pass"),
}
🤖 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 86, The "needs_attention"
field assignment at line 86 incorrectly uses bool() on candidate.get() which
misinterprets string values like "false" as True since any non-empty string is
truthy in Python. Replace the bool() coercion with explicit string comparison to
check if the value equals "true" (case-insensitive) or use proper JSON boolean
coercion instead of relying on Python's truthiness evaluation. This ensures that
string values from JSON are properly converted to their intended boolean
representation.

Comment on lines +206 to +223
if config_text is not None:
enabled, plan = parse_orchestrator_skill_config_text(config_text, config_path)
elif pack_override:
enabled = enabled_override is not False
plan = OrchestratorSkillCheckoutPlan(
repo="",
ref="",
paths=[],
checkout_path=DEFAULT_CHECKOUT_PATH,
pack=pack_override,
)

if enabled_override is not None:
enabled = enabled_override

if not enabled:
return None

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

enabled_override=true cannot re-enable configs that are disabled in JSON.

When the file has "enabled": false, parse_orchestrator_skill_config_text returns plan=None. Later setting enabled_override=True flips enabled but still returns None, so the override cannot enable existing inline/pack config.

Suggested fix
-def parse_orchestrator_skill_config(
-    payload: Any,
-) -> tuple[bool, OrchestratorSkillCheckoutPlan | None]:
+def parse_orchestrator_skill_config(
+    payload: Any,
+    *,
+    ignore_enabled: bool = False,
+) -> tuple[bool, OrchestratorSkillCheckoutPlan | None]:
@@
-    if not enabled:
+    if not enabled and not ignore_enabled:
         return False, None
@@
 def resolve_orchestrator_skill_plan(...):
@@
-    if config_text is not None:
-        enabled, plan = parse_orchestrator_skill_config_text(config_text, config_path)
+    if config_text is not None:
+        payload = json.loads(config_text)
+        enabled, plan = parse_orchestrator_skill_config(payload)
+        if enabled_override is True and plan is None:
+            _, plan = parse_orchestrator_skill_config(payload, ignore_enabled=True)
🤖 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 206 - 223, The issue is that when
a config file has "enabled": false, the parse_orchestrator_skill_config_text
function returns plan=None, and subsequently setting enabled_override=True only
flips the enabled flag but leaves plan=None, causing the function to still
return None. To fix this, after applying the enabled_override in the section
where enabled = enabled_override is set, add logic to also restore or create the
plan when enabled_override=True and plan is currently None. Check if
pack_override is available and use it to create a plan with the same structure
as in the elif pack_override block, ensuring that both enabled and plan are
properly set when the override is applied.

Comment on lines +321 to +329
for rel_path in paths:
src = clone_dir / rel_path
dst = destination_root / rel_path
if src.is_dir():
shutil.copytree(src, dst, dirs_exist_ok=True)
elif src.is_file():
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden copy step against symlink escape from cloned content.

shutil.copytree/shutil.copy2 can follow symlinks. A malicious symlink in checked-out paths can copy content outside clone_dir into workspace.

Suggested fix
         for rel_path in paths:
             src = clone_dir / rel_path
             dst = destination_root / rel_path
+            if src.is_symlink():
+                raise ValueError(f"symlinked orchestrator skill path is not allowed: {rel_path}")
             if src.is_dir():
-                shutil.copytree(src, dst, dirs_exist_ok=True)
+                shutil.copytree(src, dst, dirs_exist_ok=True, symlinks=True)
             elif src.is_file():
                 dst.parent.mkdir(parents=True, exist_ok=True)
-                shutil.copy2(src, dst)
+                shutil.copy2(src, dst, follow_symlinks=False)
📝 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
for rel_path in paths:
src = clone_dir / rel_path
dst = destination_root / rel_path
if src.is_dir():
shutil.copytree(src, dst, dirs_exist_ok=True)
elif src.is_file():
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
else:
for rel_path in paths:
src = clone_dir / rel_path
dst = destination_root / rel_path
if src.is_symlink():
raise ValueError(f"symlinked orchestrator skill path is not allowed: {rel_path}")
if src.is_dir():
shutil.copytree(src, dst, dirs_exist_ok=True, symlinks=True)
elif src.is_file():
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst, follow_symlinks=False)
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 `@scripts/runner_lib/core.py` around lines 321 - 329, The copy operations in
the file copy loop for clone_dir paths are vulnerable to symlink escape attacks
because shutil.copytree and shutil.copy2 can follow symlinks by default. Add a
check for symlinks using is_symlink() before processing each path in the loop,
and reject or skip any symlink paths to prevent malicious symlinks from copying
content outside clone_dir. Additionally, add follow_symlinks=False parameter to
the shutil.copy2 call and ensure symlinks=False is set (or explicit handling) in
the shutil.copytree call to prevent symlink traversal during the copy
operations.

Comment on lines +944 to +945
"orchestrator_skill_pack": args.orchestrator_skill_pack or None,
"orchestrator_skill_enabled": _parse_optional_bool(args.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 | 🟡 Minor | ⚡ Quick win

Normalize orchestrator_skill_pack before placing it in context.

Whitespace-only input currently becomes a truthy pack override and fails later as "pack not found". Trim here to keep behavior consistent with other override parsing.

Suggested fix
-        "orchestrator_skill_pack": args.orchestrator_skill_pack or None,
+        "orchestrator_skill_pack": (args.orchestrator_skill_pack or "").strip() or None,
🤖 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 944 - 945, The
orchestrator_skill_pack field in the context dictionary at line 944 does not
normalize whitespace-only input from args.orchestrator_skill_pack, causing
whitespace strings to be treated as valid pack overrides that later fail with
"pack not found". Normalize the args.orchestrator_skill_pack value by stripping
whitespace before assigning it to the orchestrator_skill_pack key, ensuring that
whitespace-only strings are converted to None just like other override values
are parsed.

@agents-workflows-bot
agents-workflows-bot Bot merged commit b4527bf into main Jun 20, 2026
112 of 126 checks passed
@agents-workflows-bot
agents-workflows-bot Bot deleted the sync/workflows-7456dc211466 branch June 20, 2026 06:50
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