Skip to content

feat(catalog): vendored device-catalog enrichment + auto-refresh loop - #45

Merged
simons-plugins merged 2 commits into
mainfrom
feat/catalog-enrichment
Jul 23, 2026
Merged

feat(catalog): vendored device-catalog enrichment + auto-refresh loop#45
simons-plugins merged 2 commits into
mainfrom
feat/catalog-enrichment

Conversation

@simons-plugins

@simons-plugins simons-plugins commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Closes #43, closes #44. Vendored snapshot (210 profiles, catalog@3d86e05), capabilities block in get_device_by_id, friendly capability refusals on colour/white tools (refuse only on explicit false; no profile → unchanged), new list_uncataloged_devices gap-reporter (72 tools), repository_dispatch refresh workflow + copy-in catalog-side workflow (needs LITE_DISPATCH_TOKEN PAT — Simon). Honest finding: catalog schema carries NO role/polarity fields — garage-polarity safety needs a catalog schema addition first. 450 tests. 2026.8.1 → 2026.9.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EYAGXd4bE9D9Z5kZeSHeo7

Summary by CodeRabbit

  • New Features
    • Device details now include catalog-based capability information when available.
    • Added list_uncataloged_devices to report plugin-owned devices missing catalog entries.
    • Color and white-level controls now block unsupported operations when capabilities are known.
    • MCP tool count increased to 72.
  • Bug Fixes
    • Catalog enrichment safely degrades (and avoids errors) when the vendored snapshot is missing or invalid.
  • Documentation
    • Updated tool and lookup tool counts, including list_uncataloged_devices.
  • Chores
    • Added automated catalog snapshot refresh with plugin version bump and PR updates.

…to-refresh (#43, #44)

Vendor a snapshot of the community indigo-device-catalog (210 profiles,
XKCD-table pattern: plain dict, zero network/IO at runtime) and use it
three ways:

- get_device_by_id now returns a `capabilities` block (boolean flag map)
  when the device's (pluginId, deviceTypeId) matches a catalog profile;
  the key is absent when uncataloged.
- The colour/white control tools (set_rgb_color/percent, set_hex_color,
  set_named_color, set_white_levels) pre-check the relevant capability
  flag and refuse with a friendly ValueError naming what the device DOES
  support. Advisory only: no profile, missing flag, or unreadable device
  all proceed exactly as before.
- New `list_uncataloged_devices` tool — the catalog-contribution gap
  report (plugin devices with no profile, standard paginated envelope).

Snapshot pipeline (#44): scripts/generate_catalog_snapshot.py emits a
deterministic Server Plugin/catalog_snapshot.py (provenance from the
catalog checkout's git HEAD, never the wall clock). A new
catalog-refresh workflow listens for repository_dispatch
(catalog-updated) + workflow_dispatch, regenerates from catalog@main,
and opens a patch-bump PR when the snapshot changed. The catalog-side
dispatch workflow is documented in docs/catalog-dispatch-workflow.yml
for Simon to copy into indigo-device-catalog (needs LITE_DISPATCH_TOKEN
PAT; cross-repo commit out of scope here).

Note: the catalog schema carries no role/polarity semantics (garage
inverted onState etc.) — capabilities is the whole enrichment for now;
role/polarity is a catalog-side schema follow-up.

Version 2026.8.1 → 2026.9.0; README tool table regenerated (72 tools,
completeness check green); 450 tests pass (34 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYAGXd4bE9D9Z5kZeSHeo7
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The plugin now vendors device-catalog profiles, enriches device lookups, gates unsupported colour and white controls, reports uncataloged plugin devices, and automates snapshot refreshes through GitHub Actions.

Changes

Catalog integration

Layer / File(s) Summary
Snapshot generation and validation
scripts/generate_catalog_snapshot.py, Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/catalog_snapshot.py, tests/test_catalog_snapshot_generator.py
Catalog JSON profiles are converted into deterministic static snapshot data with metadata and validation tests.
Catalog-driven control checks
Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/control.py, Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tool_registry.py, tests/test_control_capability_checks.py, tests/test_mcp_dispatch_integration.py
RGB and white-level handlers reject operations explicitly marked unsupported while allowing uncataloged or incomplete profiles to proceed.
Lookup enrichment and gap reporting
Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/catalog.py, Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/lookup.py, tests/test_catalog_profile_for.py, tests/test_lookup_*
Device details include catalog capabilities, and list_uncataloged_devices provides filtered, paginated catalog-gap results with snapshot metadata.
Refresh workflow and surface documentation
.github/workflows/catalog-refresh.yml, docs/catalog-dispatch-workflow.yml, README.md, CLAUDE.md, Indigo MCP Lite.indigoPlugin/Contents/Info.plist
Catalog updates can regenerate and publish a patch-bumped snapshot PR; tool counts, lookup documentation, and plugin version metadata are updated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant LookupTools
  participant Catalog as catalog.py
  participant Snapshot as catalog_snapshot.py
  MCPClient->>LookupTools: request device detail or uncataloged devices
  LookupTools->>Catalog: profile_for(device)
  Catalog->>Snapshot: read PROFILES by pluginId and deviceTypeId
  Snapshot-->>Catalog: matching profile or no profile
  Catalog-->>LookupTools: profile result
  LookupTools-->>MCPClient: enriched detail or paginated results
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.81% 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 concisely describes the main change: catalog enrichment plus automated refresh automation.
Linked Issues check ✅ Passed The PR implements catalog enrichment, friendly capability refusals, uncataloged reporting, and the repository_dispatch refresh loop for #43 and #44.
Out of Scope Changes check ✅ Passed No clearly unrelated changes stand out; the version, docs, and test updates all support the catalog refresh and enrichment work.
✨ 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 feat/catalog-enrichment

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

❤️ Share

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

@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/workflows/catalog-refresh.yml:
- Around line 31-35: Before enabling the catalog refresh workflow, add a
workspace ADR using the existing ADR template documenting the new GitHub network
dependency introduced by the “Clone device catalog @ main” step. Reconcile the
README’s offline vendored-snapshot description with this behavior, and keep the
workflow change gated until the ADR and documentation are updated.
- Around line 24-26: Add workflow-level concurrency for the catalog refresh
workflow, using a shared group such as chore/catalog-refresh and setting
cancel-in-progress to false. Anchor the change near the refresh-snapshot job and
preserve the existing force-push behavior while ensuring repository_dispatch and
workflow_dispatch runs execute serially.
- Around line 9-11: Update the refresh workflow’s authentication used by
checkout, push, and gh pr create so it uses a GitHub App installation token or
appropriately scoped PAT instead of the default GITHUB_TOKEN, allowing
pull_request workflows such as version-check.yml to run automatically;
alternatively, explicitly configure the affected required workflow to use manual
approval.

In `@scripts/generate_catalog_snapshot.py`:
- Around line 143-144: Update the snapshot rendering loop in the generator to
wrap long profile literals deterministically so every emitted line is at most
120 characters while preserving valid output and stable ordering. Extend
test_render_is_deterministic to assert the maximum generated line length does
not exceed 120 characters.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f1966916-8aca-4393-a91b-e6792e37c408

📥 Commits

Reviewing files that changed from the base of the PR and between 020af9a and d10e88f.

📒 Files selected for processing (16)
  • .github/workflows/catalog-refresh.yml
  • CLAUDE.md
  • Indigo MCP Lite.indigoPlugin/Contents/Info.plist
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/catalog.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/catalog_snapshot.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/control.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/lookup.py
  • README.md
  • docs/catalog-dispatch-workflow.yml
  • scripts/generate_catalog_snapshot.py
  • tests/test_catalog_profile_for.py
  • tests/test_catalog_snapshot_generator.py
  • tests/test_control_capability_checks.py
  • tests/test_lookup_catalog_enrichment.py
  • tests/test_lookup_uncataloged.py
  • tests/test_mcp_dispatch_integration.py

Comment thread .github/workflows/catalog-refresh.yml Outdated
Comment on lines +24 to +26
jobs:
refresh-snapshot:
runs-on: ubuntu-latest

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files .github/workflows || true

echo
echo "catalog-refresh workflow:"
if [ -f .github/workflows/catalog-refresh.yml ]; then
  cat -n .github/workflows/catalog-refresh.yml
else
  echo "missing .github/workflows/catalog-refresh.yml"
fi

echo
echo "Search catalog-refresh references:"
rg -n "catalog-refresh|chore/catalog-refresh|git.*(push|reset|checkout)|workflow_dispatch|schedule" .github/workflows || true

Repository: simons-plugins/indigo-mcp-lite

Length of output: 5283


🌐 Web query:

GitHub Actions concurrency group workflow cancel-in-progress fork pull request same workflow default

💡 Result:

To implement concurrency with cancel-in-progress: true in GitHub Actions for workflows that handle pull requests—including those from forks—you must carefully define your concurrency group key to ensure proper isolation and prevent unintended cancellations [1][2][3]. The concurrency key is scoped to the entire repository [4]. If multiple workflows use the same static or loosely defined key, they will cancel each other's runs regardless of the branch or PR [4][5]. Best Practices and Implementation: 1. Recommended Key Structure: Use a combination of the workflow name and the reference to ensure uniqueness per workflow and per branch/PR [2][3]. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true 2. Handling Pull Requests and Forks: Using github.ref (which typically resolves to refs/pull/:number/merge for pull requests) is preferred over github.head_ref [1]. github.head_ref is not unique across different forks; if two different users submit PRs from branches with the same name (e.g., "patch-1"), a concurrency group based solely on github.head_ref could cause one PR to cancel the other [1][4]. github.ref provides a unique identifier for every PR, preventing collisions between unrelated branches [1][3]. 3. Conditional Cancellation: To avoid cancelling runs on the main branch (where you typically want all commits to complete for deployment or release purposes) while still cancelling redundant runs on PRs, you can use an expression for cancel-in-progress [2][3]: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} 4. Alternatives for Complex Scenarios: If you need more granular control or are dealing with complex fork-based PR triggers that aren't addressed by standard concurrency groups, consider using a workflow_run event to trigger a separate, dedicated "cancellation" workflow [6][7]. This approach allows you to explicitly target and cancel specific "source" workflow runs using their ID [6][7]. Avoid using github.sha or github.run_id in your concurrency group key, as these are unique to every single run and will prevent any cancellation or queuing from occurring [3].

Citations:


🌐 Web query:

GitHub Actions actions checkout fetch-depth default pull requests head ref fetch all refs default pull_request

💡 Result:

The GitHub Actions checkout action (actions/checkout) does not fetch all refs by default [1][2]. By default, it performs a shallow fetch of only the single commit (or ref) that triggered the workflow, using a fetch-depth of 1 [1][3]. Regarding your specific scenario: 1. Default Behavior: When a workflow is triggered by a pull_request event, the default behavior of actions/checkout is to check out the merge commit (a temporary ref representing the pull request merged into the base branch) [4]. It does not automatically fetch all other branches or the complete history of the head ref [1]. 2. Fetch Depth: The fetch-depth input defaults to 1 [3][5]. Setting fetch-depth to 0 is the required configuration to fetch all history for all branches and tags [1][2]. 3. Pull Request Head Ref: If you need to work with the head ref of the pull request (rather than the default merge commit), you must explicitly specify it in your workflow configuration [5]: - uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} 4. Fetching Additional Refs: If you require additional refs or full history while using the default shallow fetch, you must manually perform additional git fetch operations (e.g., git fetch origin ) after the checkout step [6]. In summary, actions/checkout remains optimized for speed by default by minimizing the amount of data transferred [1][2]. If you require access to full history or other refs, you must explicitly adjust the fetch-depth or issue additional Git commands [1][6].

Citations:


Serialize catalog refresh to chore/catalog-refresh.

catalog-refresh has no concurrency and later uses git push --force origin "$BRANCH", so overlapping repository_dispatch/workflow_dispatch runs can reset the shared branch and leave an older catalog snapshot. Add workflow-level concurrency with cancel-in-progress: false, or serialize PR branch pushes with a shared lock.

🤖 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/catalog-refresh.yml around lines 24 - 26, Add
workflow-level concurrency for the catalog refresh workflow, using a shared
group such as chore/catalog-refresh and setting cancel-in-progress to false.
Anchor the change near the refresh-snapshot job and preserve the existing
force-push behavior while ensuring repository_dispatch and workflow_dispatch
runs execute serially.

Comment on lines +31 to +35
- name: Clone device catalog @ main
run: |
git clone --depth 1 --branch main \
https://github.com/simons-plugins/indigo-device-catalog.git \
/tmp/indigo-device-catalog

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
set -eu
rg -n -i '(catalog-refresh|indigo-device-catalog|repository_dispatch|network egress)' \
  docs/adr CLAUDE.md README.md || true

Repository: simons-plugins/indigo-mcp-lite

Length of output: 1024


🏁 Script executed:

#!/bin/sh
set -eu

printf 'Repository files matching ADR/workspace/network terms:\n'
git ls-files | rg -i '(^docs/adr|ADR|adr|README\.md|CLAUDE\.md|catalog-refresh|indigo-device-catalog|network|egress)' || true

printf '\nWorkflow relevant section:\n'
if [ -f .github/workflows/catalog-refresh.yml ]; then
  nl -ba .github/workflows/catalog-refresh.yml | sed -n '1,120p'
fi

printf '\nDocs/ADR directories:\n'
if [ -d docs/adr ]; then
  find docs/adr -maxdepth 2 -type f -print | sort
else
  find docs -maxdepth 3 -type f -print 2>/dev/null | rg -i 'adr|decision|architecture' || true
fi

Repository: simons-plugins/indigo-mcp-lite

Length of output: 536


Propose a workspace ADR before enabling catalog refresh

This workflow now clones indigo-device-catalog from GitHub, introducing new cross-repository network egress. The workspace has only FTS5 and an ADR template so far, and the README still describes an offline vendored snapshot, so document this change via a workspace ADR before merge.

🤖 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/catalog-refresh.yml around lines 31 - 35, Before enabling
the catalog refresh workflow, add a workspace ADR using the existing ADR
template documenting the new GitHub network dependency introduced by the “Clone
device catalog @ main” step. Reconcile the README’s offline vendored-snapshot
description with this behavior, and keep the workflow change gated until the ADR
and documentation are updated.

Source: Learnings

Comment on lines +143 to +144
for key in sorted(profiles):
lines.append(f" {key!r}: {profiles[key]!r},")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep generated snapshot lines within 120 characters.

Each profile is emitted as one repr line, producing many over-limit lines in catalog_snapshot.py (for example, line 22). Format the table deterministically with wrapped literals and assert the maximum generated line length in test_render_is_deterministic.

Proposed fix
 import argparse
 import json
+import pprint
 import subprocess
@@
-        "PROFILES = {",
+        "PROFILES = (",
     ]
-    for key in sorted(profiles):
-        lines.append(f"    {key!r}: {profiles[key]!r},")
-    lines.append("}")
+    lines.extend(
+        f"    {line}"
+        for line in pprint.pformat(
+            profiles, width=100, sort_dicts=True
+        ).splitlines()
+    )
+    lines.append(")")

As per coding guidelines, **/*.{py,toml} must maintain a 120-character line limit.

🤖 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/generate_catalog_snapshot.py` around lines 143 - 144, Update the
snapshot rendering loop in the generator to wrap long profile literals
deterministically so every emitted line is at most 120 characters while
preserving valid output and stable ordering. Extend test_render_is_deterministic
to assert the maximum generated line length does not exceed 120 characters.

Source: Coding guidelines

…n, generator floor, meta in gap report

Apply the six review items on the catalog-enrichment wave:

- catalog.py guards the snapshot import: a corrupt/missing
  catalog_snapshot.py now degrades to an empty catalog (profile_for
  misses, snapshot_meta {}) with a logged error instead of killing all
  72 tools at registration. Covered by a purge-and-reimport test that
  registers the full registry with a broken snapshot module.
- catalog-refresh workflow pushes and opens the PR with
  secrets.CATALOG_PR_TOKEN (falling back to github.token) so the
  automated PR triggers the required version-check CI rather than
  sitting green-by-absence; header + dispatch doc explain the secret.
- Generator gains a --min-profiles sanity floor (default 100): a
  truncated/moved catalog fails loudly instead of auto-PRing a
  near-empty snapshot.
- list_uncataloged_devices includes catalog_snapshot provenance
  (commit/date/profile_count) in the envelope so "everything
  uncataloged" is diagnosable from the response alone.
- Capability pre-check logs (debug) the swallowed device-lookup
  exception; logger threads through control.register from the
  registry.
- New tests: mixed per-argument white refusal (supportsWhite ok,
  supportsWhiteTemperature refused, names the failing flag) and a
  wire-level dispatch test proving a refusal arrives as a friendly
  isError tool result naming what the device does support.

455 tests pass (5 net new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYAGXd4bE9D9Z5kZeSHeo7

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

🧹 Nitpick comments (1)
Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/control.py (1)

232-241: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Narrow the catalog capability lookup boundary.

Catching Exception here can turn unrelated device lookup failures into a skipped capability check, letting the write continue without the catalog message it was meant to block or surface. Catch only KeyError/IndexError/ValueError from the device lookup (or a clearly isolated lower-level IOM class), leaving other exceptions to fail the JSON-RPC flow.

🤖 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 `@Indigo` MCP Lite.indigoPlugin/Contents/Server Plugin/tools/control.py around
lines 232 - 241, In the catalog capability pre-check exception handler, narrow
the catch around the device lookup to only KeyError, IndexError, and ValueError
(or the specific isolated IOM lookup exception), while preserving the existing
debug logging and skipped-check behavior for those expected failures. Allow all
other exceptions to propagate through the JSON-RPC flow.

Sources: Coding guidelines, Linters/SAST tools

🤖 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/workflows/catalog-refresh.yml:
- Around line 37-40: Update the actions/checkout configuration to set
persist-credentials to false, then explicitly configure only the required
authentication immediately before the git push and gh pr create steps; ensure
any checkout-provided credential is removed before running later scripts.

---

Nitpick comments:
In `@Indigo` MCP Lite.indigoPlugin/Contents/Server Plugin/tools/control.py:
- Around line 232-241: In the catalog capability pre-check exception handler,
narrow the catch around the device lookup to only KeyError, IndexError, and
ValueError (or the specific isolated IOM lookup exception), while preserving the
existing debug logging and skipped-check behavior for those expected failures.
Allow all other exceptions to propagate through the JSON-RPC flow.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d9b44417-ef29-4c11-9518-fa27e46896e1

📥 Commits

Reviewing files that changed from the base of the PR and between d10e88f and bc85688.

📒 Files selected for processing (12)
  • .github/workflows/catalog-refresh.yml
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/catalog.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tool_registry.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/control.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/lookup.py
  • docs/catalog-dispatch-workflow.yml
  • scripts/generate_catalog_snapshot.py
  • tests/test_catalog_profile_for.py
  • tests/test_catalog_snapshot_generator.py
  • tests/test_control_capability_checks.py
  • tests/test_lookup_uncataloged.py
  • tests/test_mcp_dispatch_integration.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • docs/catalog-dispatch-workflow.yml
  • tests/test_mcp_dispatch_integration.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/tools/lookup.py
  • tests/test_lookup_uncataloged.py
  • tests/test_catalog_snapshot_generator.py
  • Indigo MCP Lite.indigoPlugin/Contents/Server Plugin/catalog.py
  • tests/test_control_capability_checks.py
  • scripts/generate_catalog_snapshot.py

Comment on lines +37 to +40
with:
# Persist the PAT (when configured) as the push credential so
# the branch push triggers pull_request CI on the refresh PR.
token: ${{ secrets.CATALOG_PR_TOKEN || github.token }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/catalog-refresh.yml ]; then
  nl -ba .github/workflows/catalog-refresh.yml | sed -n '1,140p'
else
  echo "missing .github/workflows/catalog-refresh.yml"
fi

echo
echo "== checkout occurrences =="
rg -n "actions/checkout|persist-credentials|git push|CATALOG_PR_TOKEN|persist-credentials:false|persist-credentials:\s*false|persist-credentials:\s*true" .github/workflows catalog-refresh.yml catalog-refresh.yml 2>/dev/null || true

echo
echo "== changed files/stat =="
git diff --stat -- .github/workflows/catalog-refresh.yml 2>/dev/null || true
git diff -- .github/workflows/catalog-refresh.yml 2>/dev/null | sed -n '1,220p' || true

echo
echo "== git/config/token references =="
rg -n "\b(git (push|clone|fetch|commit|cherry-pick|rebase|apply)|CATALOG_PR_TOKEN|secrets\.CATALOG_PR_TOKEN)\b" .github/workflows catalog-refresh.yml 2>/dev/null || true

Repository: simons-plugins/indigo-mcp-lite

Length of output: 231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow excerpt =="
if [ -f .github/workflows/catalog-refresh.yml ]; then
  awk '{printf "%4d\t%s\n", NR, $0}' .github/workflows/catalog-refresh.yml | sed -n '1,160p'
else
  echo "missing .github/workflows/catalog-refresh.yml"
fi

echo
echo "== checkout and credential references =="
rg -n "actions/checkout|persist-credentials|git push|git (clone|fetch|commit|cherry-pick|rebase|apply|log|show|config)|CATALOG_PR_TOKEN|secrets\.CATALOG_PR_TOKEN" .github/workflows catalog-refresh.yml 2>/dev/null || true

echo
echo "== changed files/stat and diff =="
git diff --stat -- .github/workflows/catalog-refresh.yml 2>/dev/null || true
git diff -- .github/workflows/catalog-refresh.yml 2>/dev/null | sed -n '1,240p' || true

Repository: simons-plugins/indigo-mcp-lite

Length of output: 6396


🌐 Web query:

actions/checkout v4 persist-credentials default true README

💡 Result:

In the actions/checkout action, including version 4, the default value for the persist-credentials input is true [1][2][3]. When persist-credentials is set to true (the default), the authentication token or SSH key used by the action is configured into the local git config [1][4][5]. This allows subsequent git commands executed within the workflow job—such as git fetch or git push—to be authenticated automatically without requiring additional setup [1][4][5]. The action includes a post-job cleanup step to remove these credentials [1][6][7]. Users who wish to prevent the action from persisting these credentials can opt out by explicitly setting the input to false in their workflow configuration: with: persist-credentials: false While the default behavior remains true in version 4, it is worth noting that more recent versions (such as v6) have improved security by storing these credentials in a separate file under the runner's temporary directory rather than directly in the repository's.git/config file [4].

Citations:


Don’t let checkout persist the refresh PAT.

actions/checkout@v4 persists the token by default, so CATALOG_PR_TOKEN is available to every later command in this runner. If the PAT becomes available to an untrusted script, it can be exfiltrated and then used for the final checkout checkout push. Set persist-credentials: false and clear any checkout-provided credential before the git push/gh pr create steps, rather than leaving the checkout token configured in local git config throughout the job.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 35-40: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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/catalog-refresh.yml around lines 37 - 40, Update the
actions/checkout configuration to set persist-credentials to false, then
explicitly configure only the required authentication immediately before the git
push and gh pr create steps; ensure any checkout-provided credential is removed
before running later scripts.

Source: Linters/SAST tools

@simons-plugins
simons-plugins merged commit 3f7b2a0 into main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant