Skip to content

chore: sync workflow templates - #1204

Closed
stranske wants to merge 1 commit into
mainfrom
sync/workflows-76689bc445fd
Closed

chore: sync workflow templates#1204
stranske wants to merge 1 commit into
mainfrom
sync/workflows-76689bc445fd

Conversation

@stranske

@stranske stranske commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Sync Summary

Files Updated

  • agents-guard.yml: Agents guard - enforces agents workflow protections (Health 45)
  • maint-76-claude-code-review.yml: Claude Code review (opt-in) - runs only on labeled PRs or manual dispatch
  • reference_packs.py: Validates and resolves reference pack configuration for shared runner prompt assembly
  • 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
  • langchain_client.py: LangChain client builder - multi-provider client with slot-based fallback and configuration
  • llm_registry.py: LLM model registry helper - shared slot/model selection and blocked-model enforcement

Files Skipped

  • .github/workflows/pr-00-gate.yml: Maintains a fully custom Gate workflow; never overwrite (replaces the hard-coded custom_gate_repos list in maint-68).
  • 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: 0b04de717dcadc23aea9e2eca0b8679d27e90666
Template hash: 76689bc445fd
Sync branch: sync/workflows-76689bc445fd
Consumer repo: stranske/Manager-Database
Manifest: .github/sync-manifest.yml

Summary by CodeRabbit

  • New Features

    • Added centralized LLM configuration management module for slot resolution and model registry handling.
  • Refactor

    • Enhanced LLM client with blocked model filtering and provider/model validation.
    • Improved repository validation to enforce stricter owner/name format requirements.
    • Updated orchestrator skill materialization to conditionally include skill context based on file existence.
  • Chores

    • Updated GitHub workflow action pinned revisions.

Automated sync from stranske/Workflows
Template hash: 76689bc445fd

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

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces tools/llm_registry.py as a shared module for LLM slot/model registry logic (data structures, loading, blocklist enforcement, env overrides), refactors tools/langchain_client.py to delegate to it and enforce blocked-model guards. Fixes _validate_repo in two scripts to reject multi-segment paths. Updates scripts/runner_lib/core.py orchestrator skill materialization and prompt assembly to use a dynamic summary path. Bumps three CI action SHAs.

Changes

LLM Registry Extraction and Blocked-Model Enforcement

Layer / File(s) Summary
llm_registry: data structures, normalization, registry loading, and lookup
tools/llm_registry.py
Defines ModelRegistryEntry, SlotDefinition, normalize_provider, load_model_registry, registry_entry_for, is_model_blocked, and select_model_for_tier with file/JSON error handling and blocked-flag support.
llm_registry: slot config loading, env overrides, and resolve_slots
tools/llm_registry.py
Implements configured_model_for_provider, default_slots, load_slot_config (with tier-based model derivation and blocklist filtering), apply_slot_env_overrides (LANGCHAIN_SLOT{n} env vars), and resolve_slots as the composition entry point.
langchain_client.py: delegate to llm_registry and add blocked-model guards
tools/langchain_client.py
Replaces local provider/slot constants and parsing with llm_registry-backed thin wrappers; adds blocked-model refusal in build_chat_client for resolved and override paths; adds blocked-model early-exit in build_chat_clients for explicit-provider and multi-slot candidate paths.

Orchestrator Skill Validation and Dynamic Summary Path

Layer / File(s) Summary
_validate_repo: enforce exactly two non-empty owner/name segments
scripts/orchestrator_skill.py, scripts/reference_packs.py
Both files now split on / and require exactly two non-empty parts, rejecting inputs like a/b/c that the previous prefix/suffix checks allowed.
core.py: dynamic orchestrator summary path and conditional prompt inclusion
scripts/runner_lib/core.py
Adds contextlib import; materialize_orchestrator_skill removes the checkout directory before re-materializing; assemble_prompt captures or derives the summary path from context/ORCHESTRATOR_SKILL_SUMMARY_PATH env and resolves it relative to workspace; prompt assembly conditionally reads from that path when it exists.

CI Action Pin Bumps

Layer / File(s) Summary
Bump pinned action SHAs
.github/workflows/agents-guard.yml, .github/workflows/maint-76-claude-code-review.yml
Updates stranske/Workflows setup-api-client to d68de1904bcdbe16bfe2462b73aa18f41f8a0a47 in both event paths and bumps anthropics/claude-code-action to a new SHA.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant build_chat_client
    participant build_chat_clients
    participant llm_registry

    rect rgba(135, 206, 235, 0.5)
        Note over Caller,llm_registry: Single client construction with blocked-model check
        Caller->>build_chat_client: provider, model, model_override
        build_chat_client->>llm_registry: is_model_blocked(selected_provider, selected_model)
        alt model is blocked
            llm_registry-->>build_chat_client: True
            build_chat_client-->>Caller: None (warning logged)
        else not blocked
            llm_registry-->>build_chat_client: False
            build_chat_client-->>Caller: LangChain client
        end
    end

    rect rgba(144, 238, 144, 0.5)
        Note over Caller,llm_registry: Multi-slot construction with per-candidate blocked check
        Caller->>build_chat_clients: provider, models
        build_chat_clients->>llm_registry: load_model_registry()
        llm_registry-->>build_chat_clients: registry
        loop each slot candidate
            build_chat_clients->>llm_registry: is_model_blocked(slot_provider, slot_model)
            alt blocked
                llm_registry-->>build_chat_clients: True
                Note over build_chat_clients: skip slot, log warning
            else not blocked
                llm_registry-->>build_chat_clients: False
                build_chat_clients-->>Caller: client added to result list
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • stranske/Manager-Database#1187: Updates the same maint-76-claude-code-review.yml workflow by repinning the anthropics/claude-code-action uses: commit SHA.
  • stranske/Manager-Database#1197: Modifies the same agents-guard.yml workflow by bumping the pinned stranske/Workflows/.github/actions/setup-api-client action revision.
  • stranske/Manager-Database#1199: Introduces orchestrator-skill plumbing in scripts/orchestrator_skill.py and scripts/runner_lib/core.py that this PR's validation and dynamic summary path changes build directly upon.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.38% 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 title 'chore: sync workflow templates' is vague and overly broad, failing to specify which templates are being synced or the actual substantive changes made. Consider a more descriptive title that conveys the main changes, such as 'chore: sync workflow templates and add LLM registry helpers' or 'chore: sync workflow templates and update LLM client configuration'.
✅ 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-76689bc445fd

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

@stranske stranske mentioned this pull request Jun 22, 2026
2 tasks

@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 `@scripts/runner_lib/core.py`:
- Around line 426-431: The orchestrator summary path can be set to an absolute
path via context, which creates a security vulnerability allowing arbitrary file
inclusion. After resolving the orchestrator_summary_path (in the block starting
with the orchestrator_summary_raw assignment), add validation to ensure the
final resolved path is constrained within the workspace boundary. Use
Path.resolve() to get the absolute form of orchestrator_summary_path and verify
it is within the workspace directory using methods like is_relative_to() or by
ensuring the resolved path starts with the workspace path. If the path escapes
the workspace boundary, either reject it or raise an appropriate error.

In `@tools/langchain_client.py`:
- Around line 281-290: The blocked-model check using _is_model_blocked is
currently performed only once before the slot loop using override_provider
determined from the first slot or selected_provider. However, since
model_override can be applied to multiple different slots in the loop, each with
its own provider, the same override might be attempted with providers that
should block it. Move the blocked-model check (the if statement calling
_is_model_blocked with override_provider and model_override) into the slot loop
so it checks whether each specific slot's provider combined with model_override
is blocked before that slot attempts to use the override.

In `@tools/llm_registry.py`:
- Around line 82-96: The code lacks validation for the "quality" field within
each raw_entry before iterating over it. While a default empty dict is provided
when getting the quality field, if the actual value in the registry is null or
not a dictionary, calling .items() on quality_payload will still crash. Add a
type check to ensure quality_payload is actually a dictionary (using
isinstance(quality_payload, dict)) before attempting to iterate over
quality_payload.items() in the quality dictionary comprehension, similar to how
the code already validates that score is an int or float.
🪄 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: 75f386f3-8df9-496d-a42d-522fd02e28cc

📥 Commits

Reviewing files that changed from the base of the PR and between 1db1e55 and 89da4ae.

📒 Files selected for processing (7)
  • .github/workflows/agents-guard.yml
  • .github/workflows/maint-76-claude-code-review.yml
  • scripts/orchestrator_skill.py
  • scripts/reference_packs.py
  • scripts/runner_lib/core.py
  • tools/langchain_client.py
  • tools/llm_registry.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
🧰 Additional context used
📓 Path-based instructions (5)
**/.github/workflows/**/!(*.md)

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

For workflow_call reusable workflows, do not use top-level permissions: block as it conflicts with caller permissions (documented in docs/INTEGRATION_GUIDE.md)

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/maint-76-claude-code-review.yml
**/.github/workflows/*.{yml,yaml}

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

For startup_failure in workflows with zero jobs, check for invalid YAML syntax, invalid permission scopes, top-level permissions block on workflow_call, or circular workflow references

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/maint-76-claude-code-review.yml
.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

.github/workflows/*.yml: In this consumer repository, keep most workflow logic in stranske/Workflows and only carry repo-specific configuration unless explicitly documented as an exception
First-party consumers should reference reusable workflows with @main unless intentionally pinning to an exact commit SHA for a controlled reason

Reference reusable workflows with @main in first-party consumers unless intentionally pinning to an exact commit SHA for a controlled reason

Files:

  • .github/workflows/agents-guard.yml
  • .github/workflows/maint-76-claude-code-review.yml
.github/workflows/agents-*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

Agent workflow files (agents-*.yml) are owned by Workflows; fix issues in stranske/Workflows, not in this consumer repo

Files:

  • .github/workflows/agents-guard.yml
**/*.py

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

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

Files:

  • scripts/reference_packs.py
  • scripts/orchestrator_skill.py
  • tools/langchain_client.py
  • tools/llm_registry.py
  • scripts/runner_lib/core.py
🔀 Multi-repo context stranske/Workflows, stranske/Template

Perfect! I have enough information now to provide a comprehensive cross-repository analysis. Let me compile the findings.

Linked repositories findings

stranske/Workflows (source repository)

Repo format validation changes:

  • scripts/orchestrator_skill.py:85-89 — Updated _validate_repo() to enforce strict owner/name format by splitting on / and requiring exactly 2 non-empty components. Tests confirm intentional rejection of formats like owner/repo/extra.
  • scripts/reference_packs.py:72-76 — Identical validation change applied to reference pack repo field.
  • tests/scripts/test_reference_packs.py — Test case validates that trend/research/extra (3 segments) is rejected with "repo must use owner/name format" error [::stranske/Workflows::]
  • tests/scripts/test_orchestrator_skill.py — Test case validates that owner/repo/extra (3 segments) is rejected with matching error [::stranske/Workflows::]

LLM Registry and blocked model enforcement:

  • tools/llm_registry.py:1-276 — New module provides is_model_blocked(), normalize_provider(), select_model_for_tier(), resolve_slots(), and related helpers. Loads model registry from config/model_registry.json.
  • tools/langchain_client.py:25,102,229,283,365,489 — Now delegates provider normalization and blocked-model checks to tools.llm_registry. Returns None with warning logs when either resolved model or override model is blocked. At line 229-230: "Refusing blocked LLM model: %s/%s". At line 283-285: "Refusing blocked LLM model override". At line 365-370: Skips candidates that are blocked during multi-slot fallback. At line 489-490: Skips blocked slot overrides.
  • templates/consumer-repo/tools/llm_registry.py and templates/consumer-repo/tools/langchain_client.py — Consumer template already ships these modules synchronized [::stranske/Workflows::]

Orchestrator skill materialization changes:

  • scripts/runner_lib/core.py:materialize_orchestrator_skill() — Now suppresses FileNotFoundError when removing existing checkout directory (line: with contextlib.suppress(FileNotFoundError): shutil.rmtree(checkout_path))
  • scripts/runner_lib/core.py:assemble_prompt() — Captures returned orchestrator_summary_path from materialize_orchestrator_skill(). If not materializing, falls back to context["orchestrator_skill_summary_path"] and resolves relative paths against workspace. Only includes orchestrator content in prompt if file exists as a check (not loading fixed path). The assemble-prompt command now populates this from ORCHESTRATOR_SKILL_SUMMARY_PATH environment variable [::stranske/Workflows::]

stranske/Template (consumer repository)

No breaking impact from repo validation:

  • Template repo contains NO .github/reference_packs.json or .github/orchestrator_skill.json configs, so stricter validation will not affect it [::stranske/Template::]

Config files that will be synced:

  • config/llm_slots.json and config/model_registry.json exist and will receive updates
  • These match the producer repository structure [::stranske/Template::]

LLM client integration points:

  • tools/langchain_client.py exists and will receive the blocked-model enforcement changes
  • scripts/langchain/followup_issue_generator.py and scripts/langchain/_llm_client.py both import build_chat_client and build_chat_clients, which will now return None or empty lists for blocked models instead of proceeding [::stranske/Template::]

Summary: The PR's validation changes are safe for Template repo (no configs to break), but the new blocked-model enforcement in langchain_client is a behavioral change — callers expecting client objects will now receive None with a logged warning. The orchestrator skill summary path handling is additive (context variable fallback is optional). The stricter repo format validation is intentional per test coverage and poses no risk since test data confirms it rejects 3+ segment repos that were previously allowed.

🔇 Additional comments (5)
.github/workflows/agents-guard.yml (1)

114-114: LGTM!

Also applies to: 183-183

.github/workflows/maint-76-claude-code-review.yml (1)

192-192: LGTM!

scripts/orchestrator_skill.py (1)

83-84: LGTM!

Also applies to: 166-166

scripts/reference_packs.py (1)

86-87: LGTM!

scripts/runner_lib/core.py (1)

369-371: Checkout path sanitization is already enforced upstream.

The checkout_path values at line 369-371 come from validated sources and cannot contain absolute paths or .. traversal:

  • Reference pack case: checkout_path = ".reference/{pack.name}" where pack.name is validated to match [A-Za-z0-9._-]+ (see reference_packs.py:20)
  • Orchestrator skill case: checkout_path = DEFAULT_CHECKOUT_PATH = ".reference/orchestrator-skill" (hardcoded; see orchestrator_skill.py:30)

Both _validate_paths() functions in reference_packs.py:92-107 and orchestrator_skill.py:89-107 explicitly reject paths starting with / and containing .. segments. The construction in build_checkout_plan() and hardcoded defaults further eliminate path traversal risk.

The shutil.rmtree() call is safe.

			> Likely an incorrect or invalid review comment.

Comment on lines +426 to +431
orchestrator_summary_raw = context.get("orchestrator_skill_summary_path")
orchestrator_summary_path = (
Path(str(orchestrator_summary_raw)) if orchestrator_summary_raw else None
)
if orchestrator_summary_path and not orchestrator_summary_path.is_absolute():
orchestrator_summary_path = workspace / orchestrator_summary_path

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 orchestrator summary path to the workspace boundary.

Line 426-431 accepts an env/context-controlled path (including absolute paths), and Line 461-465 reads that file into prompt output. This enables arbitrary local file inclusion if the path source is influenced by untrusted input.

Suggested fix
@@
-    else:
-        orchestrator_summary_raw = context.get("orchestrator_skill_summary_path")
-        orchestrator_summary_path = (
-            Path(str(orchestrator_summary_raw)) if orchestrator_summary_raw else None
-        )
-        if orchestrator_summary_path and not orchestrator_summary_path.is_absolute():
-            orchestrator_summary_path = workspace / orchestrator_summary_path
+    else:
+        orchestrator_summary_raw = context.get("orchestrator_skill_summary_path")
+        orchestrator_summary_path = (
+            Path(str(orchestrator_summary_raw)) if orchestrator_summary_raw else None
+        )
+        if orchestrator_summary_path:
+            if not orchestrator_summary_path.is_absolute():
+                orchestrator_summary_path = workspace / orchestrator_summary_path
+            orchestrator_summary_path = orchestrator_summary_path.resolve()
+            workspace_resolved = workspace.resolve()
+            try:
+                orchestrator_summary_path.relative_to(workspace_resolved)
+            except ValueError as exc:
+                raise ValueError(
+                    "orchestrator_skill_summary_path must stay within workspace"
+                ) from exc

Also applies to: 461-465, 955-955

🤖 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 426 - 431, The orchestrator summary
path can be set to an absolute path via context, which creates a security
vulnerability allowing arbitrary file inclusion. After resolving the
orchestrator_summary_path (in the block starting with the
orchestrator_summary_raw assignment), add validation to ensure the final
resolved path is constrained within the workspace boundary. Use Path.resolve()
to get the absolute form of orchestrator_summary_path and verify it is within
the workspace directory using methods like is_relative_to() or by ensuring the
resolved path starts with the workspace path. If the path escapes the workspace
boundary, either reject it or raise an appropriate error.

Comment thread tools/langchain_client.py
Comment on lines +281 to 290
if model_override:
override_provider = selected_provider or (slots[0].provider if slots else "")
if override_provider and _is_model_blocked(override_provider, model_override):
logger.warning(
"Refusing blocked LLM model override: %s/%s", override_provider, model_override
)
return None
used_override = False
for slot in slots:
slot_model = model_override if model_override and not used_override else slot.model

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

Check blocked overrides against each slot provider.

This guard only checks model_override against the first resolved slot provider, but the loop can reuse that override for later providers when earlier slots lack credentials or fail. Move the blocked-model check into the slot loop so a blocked provider/model pair is never attempted.

Proposed fix
-    if model_override:
-        override_provider = selected_provider or (slots[0].provider if slots else "")
-        if override_provider and _is_model_blocked(override_provider, model_override):
-            logger.warning(
-                "Refusing blocked LLM model override: %s/%s", override_provider, model_override
-            )
-            return None
     used_override = False
     for slot in slots:
         slot_model = model_override if model_override and not used_override else slot.model
+        if _is_model_blocked(slot.provider, slot_model):
+            logger.warning("Skipping blocked LLM model override: %s/%s", slot.provider, slot_model)
+            continue
         if slot.provider == PROVIDER_OPENAI and openai_token:
🤖 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 `@tools/langchain_client.py` around lines 281 - 290, The blocked-model check
using _is_model_blocked is currently performed only once before the slot loop
using override_provider determined from the first slot or selected_provider.
However, since model_override can be applied to multiple different slots in the
loop, each with its own provider, the same override might be attempted with
providers that should block it. Move the blocked-model check (the if statement
calling _is_model_blocked with override_provider and model_override) into the
slot loop so it checks whether each specific slot's provider combined with
model_override is blocked before that slot attempts to use the override.

Comment thread tools/llm_registry.py
Comment on lines +82 to +96
entries: list[ModelRegistryEntry] = []
for raw_entry in payload.get("models", []):
if not isinstance(raw_entry, dict):
logger.warning("Ignoring invalid model registry entry in %s; expected object", path)
continue
provider = normalize_provider(str(raw_entry.get("provider", "")))
model = str(raw_entry.get("model_id", "")).strip()
if not provider or not model:
continue
quality_payload = raw_entry.get("quality", {})
quality = {
str(tier).upper(): float(score)
for tier, score in quality_payload.items()
if isinstance(score, int | float)
}

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

Validate nested registry fields before iterating.

A malformed registry such as "models": null or an entry with "quality": null still raises despite the surrounding graceful-fallback handling. Guard both fields so bad config disables registry use instead of crashing client resolution.

Proposed fix
-    entries: list[ModelRegistryEntry] = []
-    for raw_entry in payload.get("models", []):
+    raw_models = payload.get("models", [])
+    if not isinstance(raw_models, list):
+        logger.warning("Invalid model registry format in %s; expected models list", path)
+        return []
+
+    entries: list[ModelRegistryEntry] = []
+    for raw_entry in raw_models:
         if not isinstance(raw_entry, dict):
             logger.warning("Ignoring invalid model registry entry in %s; expected object", path)
             continue
@@
-        quality_payload = raw_entry.get("quality", {})
+        quality_payload = raw_entry.get("quality", {})
+        if not isinstance(quality_payload, dict):
+            logger.warning(
+                "Ignoring invalid quality scores for %s/%s in %s; expected object",
+                provider,
+                model,
+                path,
+            )
+            quality_payload = {}
         quality = {
             str(tier).upper(): float(score)
             for tier, score in quality_payload.items()
🤖 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 `@tools/llm_registry.py` around lines 82 - 96, The code lacks validation for
the "quality" field within each raw_entry before iterating over it. While a
default empty dict is provided when getting the quality field, if the actual
value in the registry is null or not a dictionary, calling .items() on
quality_payload will still crash. Add a type check to ensure quality_payload is
actually a dictionary (using isinstance(quality_payload, dict)) before
attempting to iterate over quality_payload.items() in the quality dictionary
comprehension, similar to how the code already validates that score is an int or
float.

@stranske

Copy link
Copy Markdown
Owner Author

Closing as stale: newer replacement sync PR #1208 exists from Workflows sync wave sync/workflows-591316374281 after stranske/Workflows#2498 merged.

@stranske stranske closed this Jun 22, 2026
@agents-workflows-bot
agents-workflows-bot Bot deleted the sync/workflows-76689bc445fd branch June 22, 2026 07:18
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