ci: split release into changesets and OIDC publish workflows, fix type errors#12
ci: split release into changesets and OIDC publish workflows, fix type errors#12gabrielvfonseca wants to merge 9 commits into
Conversation
- Remove v2 migration docs and v2 references from README - Update descriptions from "Frontal platform CLI" to "Frontal CLI" - Rename Homebrew formula from frontal-cli.rb to frontal.rb - Fix shebang replacement in build script - Bump version to 0.1.1 Co-Authored-By: Claude Opus 4.7 <[email protected]>
Add homepage, repository, bugs, license, and author fields. Bump version to 0.1.2. Co-Authored-By: Claude Opus 4.7 <[email protected]>
Now checks startsWith('#!') first: replaces if present, prepends if not.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Implements automated incident management and synthetic monitoring for the published CLI via GitHub Actions and Incident.io v2 API. - Synthetic monitoring every 15 min with 6 E2E user journeys - Auto create, update, and resolve incidents via Incident.io API - Flap prevention with configurable hysteresis (3 fail / 2 recover) - Status page component health sync and hourly reconciliation - PR gate validation workflow with dry-run lifecycle tests - Comprehensive ISOLATED_CONTEXT.md on-call runbook Co-Authored-By: Claude Opus 4.7 <[email protected]>
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
📝 WalkthroughWalkthroughThis PR introduces a complete synthetic CLI monitoring and incident automation system integrated with Incident.io status pages, alongside comprehensive repository and package branding reorganization. The monitoring stack includes E2E journey testing, flap-prevention state management, scheduled workflows for incident creation/update/resolve, operational runbooks, and integration validation. Supporting changes update repository URLs, package metadata, and documentation to align with the ChangesIncident.io Monitoring & Branding Consolidation
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
Greptile SummaryThis PR introduces a full synthetic monitoring and incident lifecycle system for the Frontal CLI, wiring together six GitHub Actions workflows, three shell scripts, and two config files with the Incident.io v2 API and a hysteresis-based flap state machine.
Confidence Score: 4/5Safe to merge after reviewing the two display/configuration gaps noted; no functional regression risk to the existing codebase. All of the previously-flagged blocking issues (static cache key, missing
Important Files Changed
Sequence DiagramsequenceDiagram
participant GH as GitHub Actions (cron/15min)
participant E2E as e2e-journey.sh
participant FS as flap-state.sh
participant Cache as Actions Cache
participant IC as incident-client.sh
participant IIO as Incident.io API
GH->>E2E: Run 6 CLI journeys
E2E-->>GH: run-result.json (pass/fail counts)
GH->>Cache: Restore flap-state.json (restore-keys: flap-state-prod-)
Cache-->>GH: flap-state.json (consecutive counters, incident IDs)
GH->>FS: flap-state.sh update run-result.json
FS-->>GH: "action=no-op|create|update|resolve (via GITHUB_OUTPUT)"
alt "action == create"
GH->>IC: cmd_create_internal(name, msg, severity_id)
IC->>IIO: POST /v2/incidents
IIO-->>IC: incident_id
GH->>IC: cmd_create_status_page(name, msg, status, components)
IC->>IIO: POST /v2/status_page_incidents
IIO-->>IC: status_page_incident_id
GH->>FS: Patch IDs back into flap-state.json
else "action == update"
GH->>IC: cmd_post_update(sp_id, msg, status, components)
IC->>IIO: POST /v2/status_page_incident_updates
else "action == resolve"
GH->>IC: cmd_resolve(sp_id, msg)
IC->>IIO: POST /v2/status_page_incident_updates (resolved)
GH->>IC: cmd_resolve_internal(internal_id, msg)
IC->>IIO: "PATCH /v2/incidents/{id}"
GH->>FS: Clear incident IDs from flap-state.json
end
GH->>Cache: "Save flap-state.json (key: flap-state-prod-{run_id})"
Reviews (4): Last reviewed commit: "ci: split release into changesets and OI..." | Re-trigger Greptile |
Patch incident_id / status_page_incident_id into flap-state.json after creation so update/resolve steps find real IDs instead of "null". Also fix: missing issues:write perm, undefined INFRA_CON_FAILURES, unexported failed_journeys/total outputs, dynamic needs index in gate always failing, cmd_post_update never notifying subscribers, and remove unused CodeQL advanced-setup workflow. Co-Authored-By: Claude Opus 4.7 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 18
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (3)
docs/INSTALLATION_GUIDE.md-85-85 (1)
85-85:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winTighten wording in authentication step.
Line 85 reads awkwardly (“authenticate with the Frontal”). Suggest “authenticate with Frontal” or “authenticate with the Frontal platform.”
🤖 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 `@docs/INSTALLATION_GUIDE.md` at line 85, The sentence "After installing, you need to authenticate with the Frontal:" is awkward; update that line in INSTALLATION_GUIDE.md to use clearer wording such as "After installing, you need to authenticate with Frontal:" or "After installing, you need to authenticate with the Frontal platform:" so the noun is properly styled and reads naturally.CONTRIBUTING.md-29-30 (1)
29-30:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAlign repo root naming across the same doc.
Line 30 now uses
cd cli, but the project tree still starts withfrontal-cli/(Line 89). Please update that tree root to avoid conflicting setup guidance.🤖 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 `@CONTRIBUTING.md` around lines 29 - 30, Update the repository root name so the setup steps and project tree are consistent: change the "cd cli" command to match the document's tree root "frontal-cli/" (or alternatively rename the tree root to "cli") so both references are identical; specifically update the occurrence of the shell command "cd cli" or the tree root string "frontal-cli/" in CONTRIBUTING.md so they match across the file..github/ISOLATED_CONTEXT.md-135-137 (1)
135-137:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winClarify that the API job is conditional, not always required.
This section currently reads as “all 5 jobs must pass,” but
api-connectivityis optional (test_apiinput). Tightening wording here avoids false escalation during manual validation runs.🤖 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/ISOLATED_CONTEXT.md around lines 135 - 137, Update the wording in the validate-status-page checklist to clarify that the api-connectivity job is conditional: change "Verify all 5 validation jobs pass (config, dry-run, flap logic, API, secrets)" to indicate that the API (api-connectivity) job only needs to pass when the workflow input test_api is true; reference the validate-status-page workflow and the api-connectivity job/test_api input so readers know the API job is optional during manual runs.
🧹 Nitpick comments (1)
.github/ISOLATED_CONTEXT.md (1)
114-122: ⚡ Quick winAdd a language identifier to the fenced block to satisfy markdown linting.
Suggested patch
-``` +```text Failures counter increments on each failed run, resets to 0 on any success. Successes counter increments on each successful run, resets to 0 on any failure. @@ NO-OP: everything else</details> <details> <summary>🤖 Prompt for AI Agents</summary>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/ISOLATED_CONTEXT.md around lines 114 - 122, Add a language
identifier to the fenced markdown block in ISOLATED_CONTEXT.md so the linter
recognizes it; find the fenced block containing the rules ("Failures
counter...", "Successes counter...", "CREATION: ...", etc.) and change the
opening triple-backticks to include a language token (e.g., ```text) so the
block becomes fenced with a language identifier.</details> <!-- cr-comment:v1:09c7501fda0800253e892195 --> _Source: Linters/SAST tools_ </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>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/incident/components.json:
- Line 3: The "component_id" field in .github/incident/components.json is empty
and must be populated before merge; update the component_id value for the CLI
component (the "component_id" key in that JSON object) with the stable
status-page component identifier used by your status automation so
create/update/sync operations can target the correct component.In @.github/incident/thresholds.json:
- Around line 12-14: The global timeout total_run_seconds is too low compared to
per-subprocess single_journey_seconds and will prematurely kill e2e-journey.sh
which calls run_journey eight times; update total_run_seconds in the "timeouts"
block so it is >= 8 * single_journey_seconds plus overhead (e.g., set
total_run_seconds to at least 960s + safety margin — 1200s), or compute it from
single_journey_seconds (total_run_seconds = 8 * single_journey_seconds + X) to
ensure the monitor can complete degraded runs.In @.github/scripts/e2e-journey.sh:
- Around line 212-248: compute_summary currently counts individual check entries
inside arrays (in count_status) so journeys like smoke and output contribute
multiple counts; change count_status to treat each json input (smoke_json,
auth_json, config_json, api_json, output_json, interactive_json) as one journey:
if the input is an array, consider the journey "pass" only when every element
has status=="pass" (increment passed by 1), otherwise increment failed by 1; for
singleton objects keep the existing single-object logic; update any use of
journeys_failed/journeys_passed to rely on these per-journey increments.In @.github/scripts/flap-state.sh:
- Around line 91-98: The state rewrite path currently tolerates invalid JSON on
read but always replaces STATE_FILE with the temp output, which destroys corrupt
state; change the rewrite logic that uses jq to write to a temporary file first,
check jq's exit status and validate the temp JSON (e.g., run jq -e . "$TMPFILE")
before performing mv into STATE_FILE, and on validation failure abort the mv and
keep the original file; apply the same fix to both the read/transform block
around the variables (consecutive_failures, consecutive_successes, alerting) and
the second rewrite block (the similar logic referenced for lines 182-198) so
moves only happen after successful jq execution and JSON validation.In @.github/scripts/incident-client.sh:
- Around line 381-389: The cmd_derive_severity function hardcodes buckets and
ignores .github/incident/thresholds.json; update cmd_derive_severity to read the
configured thresholds and corresponding IDs from thresholds.json (or exported
vars derived from it) and use those dynamic values to map failed_journeys to the
correct bucket (minor/major/critical) instead of always returning minor for <=1
and major otherwise; locate and change the cmd_derive_severity function to parse
thresholds.json (e.g., with jq or a small loader used elsewhere) and compare
failed_journeys against the configured thresholds, returning the matching
INCIDENT_IO_SEVERITY_* ID for the chosen bucket.- Around line 73-100: The response handling currently treats
transport/DNS/TLS/timeout failures as HTTP_STATUS=000 and returns immediately;
update the request loop that sets response and http_code to detect http_code ==
"000" (or empty) and treat it as a transient transport error that triggers the
same retry/backoff logic used for 429/5xx (use RETRY_BASE_SECS, attempt,
MAX_RETRIES and jitter logic already present), log a clear "transport error —
retrying" message and continue instead of returning; separately, fix
cmd_derive_severity() so it can emit "critical" by reading/using the critical
threshold from .github/incident/thresholds.json (or from the same threshold
parsing logic used for minor/major) and map the appropriate metric ranges to
"critical" to avoid tier drift.In @.github/workflows/flap-state-cleanup.yml:
- Around line 29-35: The workflow currently restores and saves flap-state.json
using the identical cache key "flap-state-prod" (actions/cache/restore@v4 +
actions/cache/save@v4), which prevents overwriting the cache and loses cleaned
results; change the save step to use a unique key (for example include
run-specific metadata like flap-state-prod-${{ github.run_id }} or a timestamp)
and keep the restore step using key: flap-state-prod with restore-keys:
flap-state-prod so restores use a prefix; also ensure the cleanup job (the step
that writes flap-state.json) creates the file if missing before the save step so
there's always content to persist (reference the steps named "Restore flap
state" / id: restore and the save step that currently uses flap-state-prod).In @.github/workflows/incident-manager.yml:
- Around line 109-111: The workflow currently inlines
${{ inputs.incident_message }}and${{ inputs.incident_name }}directly into Bash
calls (e.g., the cmd_resolve invocation and NAME/MESSAGE assignments), which is
unsafe; instead, add environment variables (e.g., INCIDENT_MESSAGE and
INCIDENT_NAME) in the job/stepenv:with the same default logic, and update
script usages to reference the quoted shell variables"$INCIDENT_MESSAGE"and
"$INCIDENT_NAME"(including where cmd_resolve is called) so quotes and special
characters are preserved; ensure defaults are applied in the env: assignment and
remove the inline${{ ... }}from the Bash body.- Around line 79-86: The cache step "flap-cache" currently uses a fixed key
("flap-state-prod") with actions/cache/restore@v4 which prevents updates from
being saved by actions/cache/save@v4; change the persistence strategy for
flap-state.json by either (A) making the cache key dynamic (e.g., include
github.run_id or a timestamp and matching restore-keys) so save can create a new
entry, or (B) replace the cache restore/save with actions/upload-artifact and
actions/download-artifact to store a single mutable flap-state.json artifact
across runs; update the "flap-cache" step and any corresponding save step to use
the chosen approach and ensure flap-state.json is consistently read/written by
those steps.In @.github/workflows/status-page-sync.yml:
- Around line 127-138: The heredoc in the "Generate component status snapshot"
step is breaking YAML because the EOF terminator is unindented and the
active_incident value is emitted raw (via jq -r) producing invalid JSON for
non-null IDs; fix by indenting the heredoc terminator so it remains inside the
run: | block and replace the manual heredoc + inline jq calls with a jq -n
construct that builds component-status.json using --arg/--argjson to set
generated_at, workflow_run, alerting, active_incident, consecutive_failures, and
consecutive_successes from flap-state.json (or defaults) so values are correctly
quoted/typed and the file written to component-status.json.In @.github/workflows/synthetic-monitor.yml:
- Around line 46-52: The Install Frontal CLI step fails on scheduled runs
because inputs.cli_version is empty, causing npm to install "frontal-cli@";
update the conditional and/or the npm install command to use a default when
inputs.cli_version is unset (for example change references of ${{
inputs.cli_version }} to ${{ inputs.cli_version || 'latest' }} or alter the if
check to treat empty as "latest") so the step installs frontal-cli@latest by
default when inputs.cli_version is not provided.- Around line 114-133: The current use of actions/cache (steps with id
restore-flap and the save step) won't overwrite an existing key so
flap-state.json never updates; swap to using upload/download-artifact or
otherwise ensure the key is unique per run (e.g., include run_id) so state is
actually persisted. Specifically, replace the actions/cache restore/save steps
for flap-state.json with actions/download-artifact before running
.github/scripts/flap-state.sh update (step flap) and actions/upload-artifact
after it (or modify the cache key to include github.run_id), so the
flap-state.json produced by the flap update step is the artifact persisted and
restored on the next run; keep references to flap-state.json and the
flap-state.sh update invocation intact.- Around line 98-111: The heredoc JSON for the "Verify run result exists" step
is not indented under the run: | block, breaking YAML parsing; fix by indenting
the entire heredoc payload (the lines between cat > run-result.json << 'EOF' and
EOF) so it is nested under run: | at the same indentation as the other run shell
lines, or replace the heredoc with a shell command that emits JSON (e.g., using
jq -n) to create run-result.json; target the "Verify run result exists" step and
the run: | block and ensure the run-result.json content is properly indented or
generated.In @.github/workflows/validate-status-page.yml:
- Around line 5-11: The pull_request.paths filter in the workflow's on:
pull_request block is missing the validate-status-page workflow itself; update
the paths array in .github/workflows/validate-status-page.yml (the on:
pull_request -> paths section) to include
'.github/workflows/validate-status-page.yml' so edits to this validator file
will trigger the workflow when changed.- Around line 156-158: The heredoc JSON writes using unindented EOF terminators
break YAML parsing; replace each heredoc that creates run-fail.json and
run-pass.json with a jq-based write (e.g., echoing JSON via jq --null-input or
piping a compact JSON string into jq > filename) so no multiline heredoc is
needed; update all occurrences that currently write run-fail.json and
run-pass.json (the blocks using EOF terminators) to use jq writes and keep the
surrounding shell indentation consistent.- Around line 379-399: The loop is trying to index GitHub's needs with a shell
variable using "${{ needs[format('{0}', job)].result }}" which is evaluated
before the shell runs; instead, add api-connectivity to the workflow's needs
list and pre-expand each needed job's result into shell variables (e.g., set
RES_CONFIG="${{ needs.config-validation.result }}", RES_DRY="${{
needs.dry-run-monitor.result }}", RES_FLAP="${{ needs.flap-logic-test.result
}}", RES_SECRET="${{ needs.secret-check.result }}", RES_API="${{
needs.api-connectivity.result }}") and then iterate or case-match over those
shell variables when setting FAILED, referencing the precomputed RES_*
variables; update the echo lines to include api-connectivity and use the
pre-expanded variables inside the for/if logic instead of the invalid
needs[format(...)] expression.- Around line 327-361: The step's check_secret/check_var functions use shell
indirection (${!name}) but the workflow step doesn't export the GitHub
secrets/vars into the job env, so checks always show missing; update the step
that runs these checks to map required secrets and vars into env (e.g., set
INCIDENT_IO_API_KEY, FRONTAL_TEST_API_KEY, FRONTAL_TEST_API_URL,
INCIDENT_IO_STATUS_PAGE_ID, INCIDENT_IO_SEVERITY_MINOR_ID,
INCIDENT_IO_SEVERITY_MAJOR_ID) so check_secret and check_var read real values,
and add the two missing validations (INCIDENT_IO_SEVERITY_MINOR_ID and
INCIDENT_IO_SEVERITY_MAJOR_ID) to the list of checks to ensure all used IDs are
validated by the script.In
@RELEASE.md:
- Line 94: The release doc is inconsistent: some commands reference
"frontal-cli" while line 94 uses "frontal@next"; update all occurrences of the
package identifier so the file uses a single package name (use "frontal@next" to
match line 94). Search for the literal tokens "frontal-cli" and replace them
with "frontal@next" in npm/yarn install, npx/run, global install, and any
publish/verification command examples (keep flags and surrounding command
structure unchanged).
Minor comments:
In @.github/ISOLATED_CONTEXT.md:
- Around line 135-137: Update the wording in the validate-status-page checklist
to clarify that the api-connectivity job is conditional: change "Verify all 5
validation jobs pass (config, dry-run, flap logic, API, secrets)" to indicate
that the API (api-connectivity) job only needs to pass when the workflow input
test_api is true; reference the validate-status-page workflow and the
api-connectivity job/test_api input so readers know the API job is optional
during manual runs.In
@CONTRIBUTING.md:
- Around line 29-30: Update the repository root name so the setup steps and
project tree are consistent: change the "cd cli" command to match the document's
tree root "frontal-cli/" (or alternatively rename the tree root to "cli") so
both references are identical; specifically update the occurrence of the shell
command "cd cli" or the tree root string "frontal-cli/" in CONTRIBUTING.md so
they match across the file.In
@docs/INSTALLATION_GUIDE.md:
- Line 85: The sentence "After installing, you need to authenticate with the
Frontal:" is awkward; update that line in INSTALLATION_GUIDE.md to use clearer
wording such as "After installing, you need to authenticate with Frontal:" or
"After installing, you need to authenticate with the Frontal platform:" so the
noun is properly styled and reads naturally.
Nitpick comments:
In @.github/ISOLATED_CONTEXT.md:
- Around line 114-122: Add a language identifier to the fenced markdown block in
ISOLATED_CONTEXT.md so the linter recognizes it; find the fenced block
containing the rules ("Failures counter...", "Successes counter...", "CREATION:
...", etc.) and change the opening triple-backticks to include a language token
(e.g., ```text) so the block becomes fenced with a language identifier.</details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro Plus **Run ID**: `3da140ca-d09e-45e2-a188-f8a838946941` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between f03f1a72cf74fa44874d6a2a98953703c439d75b and 3c6b7af375dd4aad403c98e3b87feb59ff2aed62. </details> <details> <summary>📒 Files selected for processing (27)</summary> * `.env.example` * `.github/ISOLATED_CONTEXT.md` * `.github/incident/components.json` * `.github/incident/thresholds.json` * `.github/scripts/e2e-journey.sh` * `.github/scripts/flap-state.sh` * `.github/scripts/incident-client.sh` * `.github/workflows/flap-state-cleanup.yml` * `.github/workflows/incident-manager.yml` * `.github/workflows/status-page-sync.yml` * `.github/workflows/synthetic-monitor.yml` * `.github/workflows/update-homebrew.yml` * `.github/workflows/validate-status-page.yml` * `CONTRIBUTING.md` * `HOMEBREW.md` * `README.md` * `RELEASE.md` * `docker-compose.yml` * `docs/ARCHITECTURE.md` * `docs/AUTHENTICATION.md` * `docs/DEVELOPERS.md` * `docs/INSTALLATION_GUIDE.md` * `docs/README.md` * `docs/V2_MIGRATION.md` * `frontal-cli.rb` * `package.json` * `tests/snapshots/cli-output.snapshot.ts` </details> <details> <summary>💤 Files with no reviewable changes (4)</summary> * .github/workflows/update-homebrew.yml * docs/V2_MIGRATION.md * frontal-cli.rb * HOMEBREW.md </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
… state Populates the empty component_id in components.json with the actual Incident.io ID (01KSZDH4708FYCMBRJWGANMYYN) scraped from the live status page, and documents it as INCIDENT_IO_CLI_COMPONENT_ID in .env.example. Adds resolve-internal command to incident-client.sh so both internal and status-page incidents are resolved on recovery. Hardens flap-state.sh with JSON validity checks before overwriting state files. Derives severity thresholds from thresholds.json instead of hardcoding. Fixes journey counting to treat the whole array as a single pass/fail unit. Switches cache keys to run-specific suffixes to prevent stale cache collisions. Co-Authored-By: Claude Opus 4.7 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
.github/scripts/incident-client.sh (1)
451-481:⚠️ Potential issue | 🔴 CriticalGuard
incident-client.shdispatcher when the script is sourced
Both.github/workflows/synthetic-monitor.ymland.github/workflows/incident-manager.ymlusesource .github/scripts/incident-client.shand then callcmd_*, but.github/scripts/incident-client.shruns an unguardedcase "${1:-}" in ... esacon load; when sourced with no subcommand args it hits the*)usage branch andexit 1, aborting the step.Proposed fix
-case "${1:-}" in - test-auth) - cmd_test_auth - ;; - create-internal) - cmd_create_internal "${2:-}" "${3:-}" "${4:-}" - ;; - create-status-page) - cmd_create_status_page "${2:-}" "${3:-}" "${4:-}" "${5:-}" - ;; - post-update) - cmd_post_update "${2:-}" "${3:-}" "${4:-}" "${5:-}" - ;; - resolve) - cmd_resolve "${2:-}" "${3:-}" - ;; - resolve-internal) - cmd_resolve_internal "${2:-}" "${3:-}" - ;; - list-active-incidents) - cmd_list_active_incidents - ;; - get-status-page-structure) - cmd_get_status_page_structure "${2:-}" - ;; - derive-severity) - cmd_derive_severity "${2:-}" - ;; - *) - echo "Usage: $0 {test-auth|create-internal|create-status-page|post-update|resolve|resolve-internal|list-active-incidents|get-status-page-structure|derive-severity}" >&2 - exit 1 - ;; -esac +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + case "${1:-}" in + test-auth) + cmd_test_auth + ;; + create-internal) + cmd_create_internal "${2:-}" "${3:-}" "${4:-}" + ;; + create-status-page) + cmd_create_status_page "${2:-}" "${3:-}" "${4:-}" "${5:-}" + ;; + post-update) + cmd_post_update "${2:-}" "${3:-}" "${4:-}" "${5:-}" + ;; + resolve) + cmd_resolve "${2:-}" "${3:-}" + ;; + resolve-internal) + cmd_resolve_internal "${2:-}" "${3:-}" + ;; + list-active-incidents) + cmd_list_active_incidents + ;; + get-status-page-structure) + cmd_get_status_page_structure "${2:-}" + ;; + derive-severity) + cmd_derive_severity "${2:-}" + ;; + *) + echo "Usage: $0 {test-auth|create-internal|create-status-page|post-update|resolve|resolve-internal|list-active-incidents|get-status-page-structure|derive-severity}" >&2 + exit 1 + ;; + esac +fi🤖 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/incident-client.sh around lines 451 - 481, The dispatcher case block currently runs on every load and calls exit when the script is sourced; wrap the entire case "${1:-}" in a guard that only executes when the script is run directly (not sourced), e.g. check BASH_SOURCE vs $0 or use a sourced-check function and only run the case when the script is the main script. Keep the existing subcommands (cmd_test_auth, cmd_create_internal, cmd_create_status_page, cmd_post_update, cmd_resolve, cmd_resolve_internal, cmd_list_active_incidents, cmd_get_status_page_structure, cmd_derive_severity) unchanged — just move that case block inside the guard so sourcing the file exposes the cmd_* functions without triggering the usage/exit branch..github/workflows/synthetic-monitor.yml (1)
90-109:⚠️ Potential issue | 🟠 MajorMake the run-result fallback reachable when the artifact is missing
actions/download-artifact@v4fails the step when the named artifact isn’t found (“No artifacts found …”), so the subsequent step that writesrun-result.jsonwon’t run in that failure mode. Allow the download step to continue (and then the file-existence check can work).Proposed fix
- name: Download run result + continue-on-error: true uses: actions/download-artifact@v4 with: name: run-result-${{ github.run_id }} path: .🤖 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/synthetic-monitor.yml around lines 90 - 109, The download-artifact step ("Download run result") currently fails the job if the named artifact is missing, preventing the following "Verify run result exists" check from creating the fallback file; update that step (the uses: actions/download-artifact@v4 invocation) to allow missing artifacts by adding the artifact option to continue (e.g., add with: if-no-files-found: ignore or warn) so the step does not fail and the subsequent run-result.json existence check can write the default result..github/workflows/incident-manager.yml (3)
106-133:⚠️ Potential issue | 🟠 MajorResolve the internal incident independently of the status-page ID.
In
.github/workflows/incident-manager.yml(force-resolve step), the internal resolve logic is nested inside theSP_IDcheck. Ifflap-state.jsonhas onlyinternal_incident_id(orstatus_page_incident_idisnull/missing), the workflow skipscmd_resolve_internalbut still runs.github/scripts/flap-state.sh resolve, which clears.internal_incident_id—leaving the internal Incident.io incident open with no pointer for later automation.Proposed fix
SP_ID="$(jq -r '.status_page_incident_id // ""' flap-state.json)" + INTERNAL_ID="$(jq -r '.internal_incident_id // ""' flap-state.json)" if [[ -n "$SP_ID" && "$SP_ID" != "null" ]]; then echo "Resolving status page incident: $SP_ID" if [[ "${{ inputs.dry_run }}" != "true" ]]; then cmd_resolve "$SP_ID" "$INCIDENT_MESSAGE" else echo "DRY RUN: would resolve $SP_ID" fi - - # Also resolve the internal incident - INTERNAL_ID="$(jq -r '.internal_incident_id // ""' flap-state.json)" - if [[ -n "$INTERNAL_ID" && "$INTERNAL_ID" != "null" ]]; then - echo "Resolving internal incident: $INTERNAL_ID" - if [[ "${{ inputs.dry_run }}" != "true" ]]; then - cmd_resolve_internal "$INTERNAL_ID" "$INCIDENT_MESSAGE" - else - echo "DRY RUN: would resolve internal $INTERNAL_ID" - fi - else - echo "No internal incident ID found — skipping internal resolve" - fi else echo "No open status page incident found in flap state" fi + + if [[ -n "$INTERNAL_ID" && "$INTERNAL_ID" != "null" ]]; then + echo "Resolving internal incident: $INTERNAL_ID" + if [[ "${{ inputs.dry_run }}" != "true" ]]; then + cmd_resolve_internal "$INTERNAL_ID" "$INCIDENT_MESSAGE" + else + echo "DRY RUN: would resolve internal $INTERNAL_ID" + fi + else + echo "No internal incident ID found — skipping internal resolve" + fi🤖 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/incident-manager.yml around lines 106 - 133, The internal-incident resolve is currently nested under the SP_ID branch so cmd_resolve_internal (and its dry-run echo) is skipped when SP_ID is missing; change the control flow so the INTERNAL_ID extraction and its handling (check of INTERNAL_ID, echo "Resolving internal incident: $INTERNAL_ID", the dry-run guard, and the cmd_resolve_internal call) run independently of the SP_ID conditional, using the same jq extraction of INTERNAL_ID and the same dry-run check, and keep the final call to .github/scripts/flap-state.sh resolve after both resolve attempts.
219-224:⚠️ Potential issue | 🟠 MajorAuto mode should fail fast instead of defaulting
flap-state.sh evaluateerrors tono-op.The auto-mode step doesn’t enable
set -euo pipefailand extractsACTIONvia a pipeline (... | grep ... | cut ...), so a failingflap-state.sh evaluate(e.g., missingjq) won’t stop the workflow and will result in an emptyACTIONthat falls back tono-op.Proposed fix
- name: Auto mode if: inputs.action == 'auto' id: auto env: INCIDENT_IO_API_KEY: ${{ secrets.INCIDENT_IO_API_KEY }} INCIDENT_IO_STATUS_PAGE_ID: ${{ vars.INCIDENT_IO_STATUS_PAGE_ID }} run: | + set -euo pipefail # In auto mode, evaluate the flap state and act accordingly # This is used when called from external automation echo "Auto mode — evaluating flap state" - ACTION="$(.github/scripts/flap-state.sh evaluate | grep '^action=' | cut -d= -f2)" + EVAL_OUTPUT="$(.github/scripts/flap-state.sh evaluate)" + ACTION="$(printf '%s\n' "$EVAL_OUTPUT" | awk -F= '$1=="action"{print $2; exit}')" ACTION="${ACTION:-no-op}" echo "Recommended action: $ACTION"🤖 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/incident-manager.yml around lines 219 - 224, Enable strict shell failure handling and stop defaulting to no-op by adding set -euo pipefail at the top of the run block and change the pipeline so failures in .github/scripts/flap-state.sh evaluate propagate instead of being masked; capture the evaluate output first (e.g., store output from flap-state.sh evaluate into a variable), check its exit status, and only then extract ACTION via grep/cut (or use a single jq extraction) so that a failed evaluate (missing jq or other error) causes the job to fail rather than falling back to ACTION="${ACTION:-no-op}".
79-86:⚠️ Potential issue | 🟠 MajorFix
flap-state.jsoncache persistence on workflow reruns (cache key uses onlygithub.run_id).On re-runs,
github.run_idstays the same, so the restore step rehydrates the previous attempt’sflap-state.json, andactions/cache/save@v4cannot overwrite the already-saved cache entry for that primary key—so the corrected state won’t persist for subsequentforce-create/force-resolveattempts.Proposed fix
- name: Restore flap state if: inputs.action != 'status-check' && inputs.action != 'force-create' id: flap-cache uses: actions/cache/restore@v4 with: path: flap-state.json - key: flap-state-prod-${{ github.run_id }} + key: flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: flap-state-prod- … - name: Save flap state if: inputs.action == 'force-resolve' || inputs.action == 'force-create' uses: actions/cache/save@v4 with: path: flap-state.json - key: flap-state-prod-${{ github.run_id }} + key: flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }}Also applies to the Save flap state step.
🤖 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/incident-manager.yml around lines 79 - 86, The cache key uses only github.run_id so reruns reuse the same primary key and prevent saving an updated flap-state.json; update both the Restore step (id flap-cache, uses actions/cache/restore@v4) and the Save step (uses actions/cache/save@v4) to include github.run_attempt (or another per-attempt identifier) in the primary key — e.g. change key from flap-state-prod-${{ github.run_id }} to flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }} and keep restore-keys: flap-state-prod- so the restore still finds older caches but saves create a distinct entry per attempt..github/workflows/flap-state-cleanup.yml (1)
29-35:⚠️ Potential issue | 🟠 MajorCleanup reruns must include
github.run_attemptin the cache keyWith
github.run_idalone, a rerun restores the prior attempt’sflap-state.json, andactions/cache/save@v4will not overwrite the existing cache entry—so the rerun’s corrected cleaned state won’t be persisted.Proposed fix
- name: Restore flap state id: restore uses: actions/cache/restore@v4 with: path: flap-state.json - key: flap-state-prod-${{ github.run_id }} + key: flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: flap-state-prod- … - name: Save cleaned state uses: actions/cache/save@v4 with: path: flap-state.json - key: flap-state-prod-${{ github.run_id }} + key: flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }}Also applies to lines 94-98.
🤖 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/flap-state-cleanup.yml around lines 29 - 35, The cache key for the flap-state restore/save uses only github.run_id which causes reruns to pick up a previous attempt; update the cache key in the actions/cache/restore@v4 step (id: restore) and the matching actions/cache/save@v4 step to include github.run_attempt, e.g. change flap-state-prod-${{ github.run_id }} to flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }} and adjust restore-keys accordingly so the restore and save keys match the new pattern..github/workflows/validate-status-page.yml (2)
376-395:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake
api-connectivityactually fail the gate when the explicit API test fails.Adding
api-connectivitytoneedsis ineffective right now because that job still exits successfully on401and other non-200responses. Whentest_apiis enabled, the workflow can stay green with broken Incident.io auth.Suggested patch
- name: Test Incident.io API auth id: api-test env: INCIDENT_IO_API_KEY: ${{ secrets.INCIDENT_IO_API_KEY }} run: | if [[ -z "$INCIDENT_IO_API_KEY" ]]; then echo "INCIDENT_IO_API_KEY not configured — skipping" exit 0 fi echo "Testing Incident.io API authentication..." RESPONSE="$(curl -s -w '\n%{http_code}' \ -H "Authorization: Bearer $INCIDENT_IO_API_KEY" \ https://api.incident.io/v2/identity)" HTTP_CODE="$(echo "$RESPONSE" | tail -1)" BODY="$(echo "$RESPONSE" | sed '$d')" echo "HTTP status: $HTTP_CODE" if [[ "$HTTP_CODE" == "200" ]]; then echo "API authentication: OK" echo "$BODY" | jq '.identity.roles // "roles not in response"' 2>/dev/null || echo "$BODY" elif [[ "$HTTP_CODE" == "401" ]]; then - echo "WARNING: API key is invalid or expired" + echo "ERROR: API key is invalid or expired" + exit 1 else - echo "WARNING: Unexpected response ($HTTP_CODE)" + echo "ERROR: Unexpected response ($HTTP_CODE)" + exit 1 fi🤖 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/validate-status-page.yml around lines 376 - 395, The api-connectivity job currently treats non-200 responses (e.g. 401) as success, so add explicit failure logic in the api-connectivity job's API test step (the step that runs when test_api is enabled) to exit non-zero on any HTTP status other than 200; update that step to either use a failing HTTP client flag (e.g. curl -f) or capture the response code and call exit 1 when it != 200 so the job fails and the needs.api-connectivity.result becomes "failure", causing the gate to fail as intended.
316-371:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
secret-checkstill doesn't enforce the required pre-merge config.This step now reads the right
envvalues, but it never exits non-zero when required secrets/vars are missing. That means the gate can pass even though the Incident.io integration is not deployable, which contradicts the PR's stated pre-merge requirement to configure these values.If fork PRs need to stay non-blocking, make the failure conditional on
workflow_dispatchor same-repo PRs instead of always succeeding.🤖 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/validate-status-page.yml around lines 316 - 371, The check step currently never fails when required secrets/vars are missing (missing stays checked but no non-zero exit), so update the "Check required secrets" step to exit non-zero when missing==1 but only for non-fork / explicit runs: add environment inputs for github.event_name and the PR head repo (e.g. GITHUB_EVENT_NAME: ${{ github.event_name }}, PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}), then after the existing checks replace the final missing branch to: if missing==1 then if GITHUB_EVENT_NAME == "workflow_dispatch" OR PR_HEAD_REPO == "${{ github.repository }}" then echo an error and exit 1 else echo a non-blocking warning and exit 0; otherwise keep the success message. Ensure you reference and modify the check_secret and check_var logic and the missing variable handling in that step.
♻️ Duplicate comments (1)
.github/workflows/synthetic-monitor.yml (1)
112-118:⚠️ Potential issue | 🟠 MajorUse
github.run_attemptin the flap-state cache key.
github.run_idstays constant across re-runs, so the cache restore loads the previous attempt’sflap-state.json, andactions/cache/save@v4can’t overwrite the existing exact-key cache—so the updated flap state from the rerun is not persisted.Applies to both the Restore flap state step (112-118) and the Save flap state step (324-329).
Proposed fix
- name: Restore flap state id: restore-flap uses: actions/cache/restore@v4 with: path: flap-state.json - key: flap-state-prod-${{ github.run_id }} + key: flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: flap-state-prod- … - name: Save flap state if: always() uses: actions/cache/save@v4 with: path: flap-state.json - key: flap-state-prod-${{ github.run_id }} + key: flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }}🤖 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/synthetic-monitor.yml around lines 112 - 118, The cache key for the "Restore flap state" step (id: restore-flap) uses github.run_id which is constant across reruns; update the key to include github.run_attempt (e.g., flap-state-prod-${{ github.run_id }}-${{ github.run_attempt }}) so each attempt has a unique exact-key and the subsequent "Save flap state" step (the flap-state save step) can overwrite and persist the updated flap-state.json; make the same change to the Save flap state step's key/restore-keys to match.
🤖 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/synthetic-monitor.yml:
- Around line 47-50: The Install Frontal CLI step in the workflow is inlining
the GitHub Actions expression into the bash run (VERSION="${{ inputs.cli_version
|| 'latest' }}"), which can lead to shell injection; change the step to pass the
computed value via env (set VERSION: ${{ inputs.cli_version || 'latest' }}) and
then reference that environment variable inside the run block (use $VERSION in
the npm install command) so inputs.cli_version is not expanded into the shell
directly; update the step labelled "Install Frontal CLI" and the run invocation
that currently uses npm install -g "frontal-cli@${VERSION}" to instead read
VERSION from env.
---
Outside diff comments:
In @.github/scripts/incident-client.sh:
- Around line 451-481: The dispatcher case block currently runs on every load
and calls exit when the script is sourced; wrap the entire case "${1:-}" in a
guard that only executes when the script is run directly (not sourced), e.g.
check BASH_SOURCE vs $0 or use a sourced-check function and only run the case
when the script is the main script. Keep the existing subcommands
(cmd_test_auth, cmd_create_internal, cmd_create_status_page, cmd_post_update,
cmd_resolve, cmd_resolve_internal, cmd_list_active_incidents,
cmd_get_status_page_structure, cmd_derive_severity) unchanged — just move that
case block inside the guard so sourcing the file exposes the cmd_* functions
without triggering the usage/exit branch.
In @.github/workflows/flap-state-cleanup.yml:
- Around line 29-35: The cache key for the flap-state restore/save uses only
github.run_id which causes reruns to pick up a previous attempt; update the
cache key in the actions/cache/restore@v4 step (id: restore) and the matching
actions/cache/save@v4 step to include github.run_attempt, e.g. change
flap-state-prod-${{ github.run_id }} to flap-state-prod-${{ github.run_id }}-${{
github.run_attempt }} and adjust restore-keys accordingly so the restore and
save keys match the new pattern.
In @.github/workflows/incident-manager.yml:
- Around line 106-133: The internal-incident resolve is currently nested under
the SP_ID branch so cmd_resolve_internal (and its dry-run echo) is skipped when
SP_ID is missing; change the control flow so the INTERNAL_ID extraction and its
handling (check of INTERNAL_ID, echo "Resolving internal incident:
$INTERNAL_ID", the dry-run guard, and the cmd_resolve_internal call) run
independently of the SP_ID conditional, using the same jq extraction of
INTERNAL_ID and the same dry-run check, and keep the final call to
.github/scripts/flap-state.sh resolve after both resolve attempts.
- Around line 219-224: Enable strict shell failure handling and stop defaulting
to no-op by adding set -euo pipefail at the top of the run block and change the
pipeline so failures in .github/scripts/flap-state.sh evaluate propagate instead
of being masked; capture the evaluate output first (e.g., store output from
flap-state.sh evaluate into a variable), check its exit status, and only then
extract ACTION via grep/cut (or use a single jq extraction) so that a failed
evaluate (missing jq or other error) causes the job to fail rather than falling
back to ACTION="${ACTION:-no-op}".
- Around line 79-86: The cache key uses only github.run_id so reruns reuse the
same primary key and prevent saving an updated flap-state.json; update both the
Restore step (id flap-cache, uses actions/cache/restore@v4) and the Save step
(uses actions/cache/save@v4) to include github.run_attempt (or another
per-attempt identifier) in the primary key — e.g. change key from
flap-state-prod-${{ github.run_id }} to flap-state-prod-${{ github.run_id }}-${{
github.run_attempt }} and keep restore-keys: flap-state-prod- so the restore
still finds older caches but saves create a distinct entry per attempt.
In @.github/workflows/synthetic-monitor.yml:
- Around line 90-109: The download-artifact step ("Download run result")
currently fails the job if the named artifact is missing, preventing the
following "Verify run result exists" check from creating the fallback file;
update that step (the uses: actions/download-artifact@v4 invocation) to allow
missing artifacts by adding the artifact option to continue (e.g., add with:
if-no-files-found: ignore or warn) so the step does not fail and the subsequent
run-result.json existence check can write the default result.
In @.github/workflows/validate-status-page.yml:
- Around line 376-395: The api-connectivity job currently treats non-200
responses (e.g. 401) as success, so add explicit failure logic in the
api-connectivity job's API test step (the step that runs when test_api is
enabled) to exit non-zero on any HTTP status other than 200; update that step to
either use a failing HTTP client flag (e.g. curl -f) or capture the response
code and call exit 1 when it != 200 so the job fails and the
needs.api-connectivity.result becomes "failure", causing the gate to fail as
intended.
- Around line 316-371: The check step currently never fails when required
secrets/vars are missing (missing stays checked but no non-zero exit), so update
the "Check required secrets" step to exit non-zero when missing==1 but only for
non-fork / explicit runs: add environment inputs for github.event_name and the
PR head repo (e.g. GITHUB_EVENT_NAME: ${{ github.event_name }}, PR_HEAD_REPO:
${{ github.event.pull_request.head.repo.full_name }}), then after the existing
checks replace the final missing branch to: if missing==1 then if
GITHUB_EVENT_NAME == "workflow_dispatch" OR PR_HEAD_REPO == "${{
github.repository }}" then echo an error and exit 1 else echo a non-blocking
warning and exit 0; otherwise keep the success message. Ensure you reference and
modify the check_secret and check_var logic and the missing variable handling in
that step.
---
Duplicate comments:
In @.github/workflows/synthetic-monitor.yml:
- Around line 112-118: The cache key for the "Restore flap state" step (id:
restore-flap) uses github.run_id which is constant across reruns; update the key
to include github.run_attempt (e.g., flap-state-prod-${{ github.run_id }}-${{
github.run_attempt }}) so each attempt has a unique exact-key and the subsequent
"Save flap state" step (the flap-state save step) can overwrite and persist the
updated flap-state.json; make the same change to the Save flap state step's
key/restore-keys to match.
🪄 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: 9eb8ef47-6838-4590-bb14-5b21ac423606
📒 Files selected for processing (11)
.env.example.github/incident/components.json.github/incident/thresholds.json.github/scripts/e2e-journey.sh.github/scripts/flap-state.sh.github/scripts/incident-client.sh.github/workflows/flap-state-cleanup.yml.github/workflows/incident-manager.yml.github/workflows/status-page-sync.yml.github/workflows/synthetic-monitor.yml.github/workflows/validate-status-page.yml
🚧 Files skipped from review as they are similar to previous changes (6)
- .github/incident/components.json
- .env.example
- .github/incident/thresholds.json
- .github/workflows/status-page-sync.yml
- .github/scripts/e2e-journey.sh
- .github/scripts/flap-state.sh
| - name: Install Frontal CLI | ||
| run: | | ||
| VERSION="${{ inputs.cli_version || 'latest' }}" | ||
| npm install -g "frontal-cli@${VERSION}" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and print the relevant section of the workflow
WORKFLOW=".github/workflows/synthetic-monitor.yml"
if [ ! -f "$WORKFLOW" ]; then
echo "Missing file: $WORKFLOW" >&2
exit 1
fi
echo "=== File: $WORKFLOW (lines 1-120) ==="
nl -ba "$WORKFLOW" | sed -n '1,120p'
echo "=== Extract lines 40-80 ==="
nl -ba "$WORKFLOW" | sed -n '40,80p'Repository: frontal-labs/cli
Length of output: 190
🏁 Script executed:
#!/bin/bash
set -euo pipefail
WORKFLOW=".github/workflows/synthetic-monitor.yml"
if [ ! -f "$WORKFLOW" ]; then
echo "Missing file: $WORKFLOW" >&2
exit 1
fi
echo "=== Extract lines 1-120 ==="
cat -n "$WORKFLOW" | sed -n '1,120p'
echo "=== Extract lines 35-70 ==="
cat -n "$WORKFLOW" | sed -n '35,70p'Repository: frontal-labs/cli
Length of output: 5625
Avoid inlining inputs.cli_version directly in the bash script
VERSION="${{ inputs.cli_version || 'latest' }}" is expression-expanded before bash parsing, so a crafted workflow_dispatch value can introduce bash syntax (e.g., command substitution) and execute arbitrary commands. Pass it via env: and use the environment variable in the install command.
Proposed fix
- name: Install Frontal CLI
+ env:
+ CLI_VERSION: ${{ inputs.cli_version || 'latest' }}
run: |
- VERSION="${{ inputs.cli_version || 'latest' }}"
- npm install -g "frontal-cli@${VERSION}"
+ npm install -g "frontal-cli@${CLI_VERSION}"
frontal --version🧰 Tools
🪛 zizmor (1.25.2)
[error] 49-49: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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/synthetic-monitor.yml around lines 47 - 50, The Install
Frontal CLI step in the workflow is inlining the GitHub Actions expression into
the bash run (VERSION="${{ inputs.cli_version || 'latest' }}"), which can lead
to shell injection; change the step to pass the computed value via env (set
VERSION: ${{ inputs.cli_version || 'latest' }}) and then reference that
environment variable inside the run block (use $VERSION in the npm install
command) so inputs.cli_version is not expanded into the shell directly; update
the step labelled "Install Frontal CLI" and the run invocation that currently
uses npm install -g "frontal-cli@${VERSION}" to instead read VERSION from env.
Source: Linters/SAST tools
Co-Authored-By: Claude Opus 4.7 <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/publish.yml:
- Line 18: The workflow uses floating action tags (actions/checkout@v4,
oven-sh/setup-bun@v2, softprops/action-gh-release@v2) and leaves checkout
credentials persisted; update each action reference to a full 40-character
commit SHA instead of the tag (replace actions/checkout@v4,
oven-sh/setup-bun@v2, softprops/action-gh-release@v2 with their exact commit
SHAs), and for the checkout step (actions/checkout) add a with:
persist-credentials: false entry to disable credential persistence during the
job; ensure you update the three action identifiers in the file where those
exact strings appear.
- Around line 17-23: The Checkout step using actions/checkout@v4 currently
leaves the workflow token persisted (default true); update the Checkout step
(named "Checkout") to set persist-credentials: false so the runner does not
store the workflow token in local git config for subsequent steps; ensure the
persist-credentials key is added under the Checkout step alongside uses:
actions/checkout@v4.
In @.github/workflows/release.yml:
- Line 38: Replace the mutable tag uses: changesets/action@v1 with an immutable
pinned commit SHA (e.g., uses: changesets/action@<full-commit-sha>) so the
workflow uses a specific commit; update the single occurrence of
"changesets/action@v1" in the release workflow to the chosen full commit SHA and
commit the change to ensure the action is not using a mutable tag.
In `@RELEASE.md`:
- Around line 75-76: The RELEASE.md statement that both registries publish
"using OIDC" is incorrect; update the wording to reflect that in publish.yml the
external registry uses OIDC whereas GitHub Packages is published using the
GITHUB_TOKEN. Edit the sentence referencing publish.yml to mention OIDC for the
external registry and explicitly call out GITHUB_TOKEN for GitHub Packages (and
optionally reference the GITHUB_TOKEN usage already documented elsewhere).
- Around line 72-76: The RELEASE.md currently documents both the new
two-workflow automation (release.yml and publish.yml) and the old manual command
(`bun run release`), which conflicts and can cause duplicate/partial releases;
update RELEASE.md to remove or modify the manual `bun run release` instructions
(references to the command and any step at Line 55/167) so manual release
guidance explicitly matches the new model: either instruct operators to
open/merge the "Release Packages" PR (release.yml) or to create/push a semantic
`v*` git tag to trigger publish.yml, and ensure the doc mentions the "Release
Packages" PR, release.yml, publish.yml, and the v* tag as the unique triggers to
avoid bypassing publish.yml.
🪄 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: bc36e1c7-0ad6-48ac-8e32-2d47e605dce8
📒 Files selected for processing (7)
.github/workflows/publish.yml.github/workflows/release.ymlRELEASE.mdsrc/config/schema.tssrc/schemas/frontal-json.tstests/utils/mocks.tstsconfig.json
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@v2 | ||
| with: | ||
| bun-version: latest |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
ls -la .github/workflows || true
sed -n '1,120p' .github/workflows/publish.ymlRepository: frontal-labs/cli
Length of output: 3429
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "actions/checkout@|persist-credentials" -S .github/workflows || trueRepository: frontal-labs/cli
Length of output: 1352
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,160p' .github/workflows/publish.ymlRepository: frontal-labs/cli
Length of output: 2700
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "persist-credentials|actions/checkout@" .github/workflows -SRepository: frontal-labs/cli
Length of output: 1352
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,200p' .github/workflows/publish.ymlRepository: frontal-labs/cli
Length of output: 2700
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "persist-credentials|actions/checkout@" .github/workflows -SRepository: frontal-labs/cli
Length of output: 1352
🌐 Web query:
actions/checkout persist-credentials default true writes token to git config
💡 Result:
In versions of actions/checkout prior to v6, the persist-credentials option (which defaults to true) would indeed write the authentication token directly into the local.git/config file [1][2][3]. However, starting with version v6, the behavior was updated to improve security [1][4][5]. Instead of writing the token directly into.git/config, the action now stores the credentials in a separate file located under $RUNNER_TEMP [1][4][5]. The action then uses git config mechanisms (such as include.path) to reference this separate file [5]. In both cases, the objective remains the same: to allow subsequent steps in your workflow to run authenticated git commands (like git fetch or git push) without needing to manually handle the credentials [1][2][6]. The token is removed during post-job cleanup regardless of the storage method [1][2][6]. You can opt out of this behavior by setting persist-credentials to false [1][2][6].
Citations:
- 1: https://github.com/actions/checkout
- 2: https://github.com/actions/checkout/?tab=readme-ov-file
- 3: Remove
persist-credentialsor change the default tofalseactions/checkout#485 - 4: https://github.com/actions/checkout/tree/refs/heads/main
- 5: actions/checkout@v5.0.1...v6.0.0
- 6: https://github.com/actions/checkout/blob/2541b1294d2704b0964813337f33b291d3f8596b/README.md
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "persist-credentials" -S .github/workflows || trueRepository: frontal-labs/cli
Length of output: 42
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "persist-credentials" -S .github/workflows || trueRepository: frontal-labs/cli
Length of output: 42
🌐 Web query:
actions/checkout persist-credentials default true writes token to git config
💡 Result:
The actions/checkout action, when persist-credentials is set to the default value of true, enables your scripts to run authenticated git commands by configuring the authentication token or SSH key with the local git environment [1][2]. While earlier versions of actions/checkout directly wrote the token into.git/config, recent updates have improved credential security by storing the credentials in a separate file located under $RUNNER_TEMP instead of directly within.git/config [2][3]. The token remains associated with the job and is removed during post-job cleanup [4][5]. If you wish to prevent this behavior, you can set persist-credentials to false in your workflow step [4][1]: - uses: actions/checkout@v4 with: persist-credentials: false
Citations:
- 1: https://github.com/actions/checkout/blob/34e114876b0b11c390a56381ad16ebd13914f8d5/action.yml
- 2: https://github.com/actions/checkout/tree/v6.0.2?tab=readme-ov-file
- 3: https://github.com/actions/checkout/tree/refs/heads/main
- 4: https://github.com/actions/checkout
- 5: https://github.com/actions/checkout/?tab=readme-ov-file
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "persist-credentials" -S .github/workflows || trueRepository: frontal-labs/cli
Length of output: 42
Disable persisted Git credentials during checkout (actions/checkout@v4).
The checkout step omits persist-credentials, so the default (true) persists the workflow token in the runner’s local git configuration for the rest of the job. Set persist-credentials: false.
Suggested patch
- - name: Checkout
- uses: actions/checkout@v4
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: 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.
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest |
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 17-18: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 21-21: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
🤖 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/publish.yml around lines 17 - 23, The Checkout step using
actions/checkout@v4 currently leaves the workflow token persisted (default
true); update the Checkout step (named "Checkout") to set persist-credentials:
false so the runner does not store the workflow token in local git config for
subsequent steps; ensure the persist-credentials key is added under the Checkout
step alongside uses: actions/checkout@v4.
Source: Linters/SAST tools
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/publish.yml"
echo "=== File exists? ==="
ls -la "$FILE" || true
echo
echo "=== Workflow: publish.yml (lines with context) ==="
nl -ba "$FILE" | sed -n '1,120p'
echo
echo "=== Extract all `uses:` lines ==="
rg -n '^\s*uses:\s*' "$FILE" || true
echo
echo "=== Check checkout persist-credentials setting ==="
rg -n 'persist-credentials' "$FILE" || trueRepository: frontal-labs/cli
Length of output: 274
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/publish.yml"
echo "=== Workflow: publish.yml with line numbers (first ~140 lines) ==="
awk '{print NR "\t" $0}' "$FILE" | sed -n '1,140p'
echo
echo "=== Extract all `uses:` lines ==="
rg -n '^\s*uses:\s*' "$FILE" || true
echo
echo "=== Check checkout persist-credentials setting ==="
rg -n 'persist-credentials' "$FILE" || true
echo
echo "=== Check write permissions / permissions block (if present) ==="
rg -n 'permissions:' "$FILE" || true
echo
echo "=== Check for checkout with explicit inputs (if any) ==="
rg -n 'actions/checkout@|with:\s*$' "$FILE" || trueRepository: frontal-labs/cli
Length of output: 3522
🌐 Web query:
actions/checkout persist-credentials default value
💡 Result:
The default value for the persist-credentials input in the actions/checkout GitHub Action is true [1][2][3]. When this option is enabled (which is the default behavior), the action configures the authentication token or SSH key with the local git configuration [4][3]. This allows your workflow scripts to run authenticated git commands, such as git fetch or git push [4][5]. The action automatically removes these credentials during the post-job cleanup [4][6]. You can set persist-credentials to false to opt out of this behavior [4][7].
Citations:
- 1: https://github.com/actions/checkout/blob/34e114876b0b11c390a56381ad16ebd13914f8d5/action.yml
- 2: https://github.com/actions/checkout/blob/85e6279cec87321a52edac9c87bce653a07cf6c2/README.md
- 3: https://github.com/actions/checkout/blob/72f2cec99f417b1a1c5e2e88945068983b7965f9/action.yml
- 4: https://github.com/actions/checkout/?tab=readme-ov-file
- 5: https://github.com/actions/checkout
- 6: https://github.com/actions/checkout/tree/refs/heads/main
- 7: https://github.com/marketplace/actions/checkout
🌐 Web query:
GitHub Actions pin third-party actions to commit SHAs security guidance
💡 Result:
GitHub explicitly recommends pinning third-party actions to a full-length commit SHA as the only way to treat an action as an immutable release [1][2]. Security Rationale Using tags (e.g., @v4) or branches (e.g., @main) for third-party actions is inherently risky because these references are mutable [3][4]. If an action's repository is compromised or a maintainer's account is taken over, an attacker can force-push a new commit to an existing tag, effectively replacing the code executed by your workflow [5][3]. Pinning to a full-length (40-character) commit SHA provides an immutable, content-addressed reference [5][3]. This ensures that your workflow consistently runs the exact code you audited, mitigating the risk of supply chain attacks where malicious code is injected via tag updates [1][6]. Policy Enforcement GitHub enables administrators to enforce this security practice at the enterprise, organization, and repository levels [7][2]. When SHA pinning is enforced via policy, any workflow attempting to use a mutable reference (like a tag or branch) will fail, preventing the use of unpinned actions [7][6]. Recommended Best Practices 1. Pin to Full SHAs: Always use the full 40-character commit SHA rather than a short SHA to ensure complete immutability [1][5]. 2. Automate Updates: Because SHA pinning creates a maintenance burden, use tools like Dependabot or Renovate to manage updates [5][3]. These tools can automatically open pull requests when new versions are released, allowing you to review the changes before updating the pinned SHA [3][6]. 3. Verify the Source: When selecting a SHA, ensure it originates from the official action repository and not a fork [1]. 4. Trusting Tags: While pinning to tags is common for convenience, it should only be done if you fully trust the action creator [1][2]. Even in these cases, SHA pinning remains the superior security standard [4]. 5. Audit Sub-actions: Be aware that the SHA pinning policy also evaluates the dependency tree. If you maintain your own actions, ensure that any actions you reference internally are also pinned to SHAs to avoid breaking your users' workflows [6].
Citations:
- 1: https://docs.github.com/en/actions/reference/security/secure-use
- 2: https://github.com/github/docs/blob/main/content/actions/reference/security/secure-use.md
- 3: https://www.systemshardening.com/articles/cicd/github-actions-supply-chain-hardening/
- 4: https://www.stepsecurity.io/blog/pinning-github-actions-for-enhanced-security-a-complete-guide
- 5: https://safeguard.sh/resources/blog/how-to-pin-github-actions-to-shas-correctly
- 6: https://www.romainlespinasse.dev/posts/github-actions-commit-sha-pinning/
- 7: https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
Pin GitHub Actions to immutable commit SHAs (and disable checkout credential persistence).
- Line 18 uses
actions/checkout@v4, Line 21 usesoven-sh/setup-bun@v2, and Line 63 usessoftprops/action-gh-release@v2— replace each with a full 40-char commit SHA. - Line 18 does not set
with: persist-credentials: false; defaultpersist-credentials: trueconfigures auth in git for the duration of the job, increasing blast radius.
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 17-18: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 18-18: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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/publish.yml at line 18, The workflow uses floating action
tags (actions/checkout@v4, oven-sh/setup-bun@v2, softprops/action-gh-release@v2)
and leaves checkout credentials persisted; update each action reference to a
full 40-character commit SHA instead of the tag (replace actions/checkout@v4,
oven-sh/setup-bun@v2, softprops/action-gh-release@v2 with their exact commit
SHAs), and for the checkout step (actions/checkout) add a with:
persist-credentials: false entry to disable credential persistence during the
job; ensure you update the three action identifiers in the file where those
exact strings appear.
Source: Linters/SAST tools
| - name: Create Release Pull Request or Publish | ||
| - name: Create Release Pull Request or Publish to NPM | ||
| id: changesets | ||
| uses: changesets/action@v1 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show relevant portion of the workflow with line numbers
nl -ba .github/workflows/release.yml | sed -n '20,70p'
# Find all occurrences of changesets/action in workflows
rg -n "uses:\s*changesets/action@" .github/workflows || true
# Also check if there's any pinned SHA usage pattern for changesets/action
rg -n "changesets/action@([0-9a-f]{7,40})" .github/workflows || trueRepository: frontal-labs/cli
Length of output: 103
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show relevant portion of the workflow with line numbers
nl -ba .github/workflows/release.yml | sed -n '20,70p'
# Find all occurrences of changesets/action in workflows
rg -n "uses:\s*changesets/action@" .github/workflows || true
# Also check if there's any pinned SHA usage pattern for changesets/action
rg -n "changesets/action@([0-9a-f]{7,40})" .github/workflows || trueRepository: frontal-labs/cli
Length of output: 103
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show relevant portion of the workflow with line numbers (no `nl` in this sandbox)
cat -n .github/workflows/release.yml | sed -n '20,70p'
# Find all occurrences of changesets/action in workflows
rg -n "uses:\s*changesets/action@" .github/workflows || true
# Check for pinned SHA usage of changesets/action elsewhere
rg -n "changesets/action@[0-9a-f]{7,40}" .github/workflows || trueRepository: frontal-labs/cli
Length of output: 1019
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== All changesets/action usages =="
rg -n "uses:\s*changesets/action@" .github/workflows || echo "NONE"
echo
echo "== Pinned SHA changesets/action usages =="
rg -n "changesets/action@[0-9a-f]{7,40}" .github/workflows || echo "NONE"Repository: frontal-labs/cli
Length of output: 211
Pin changesets/action to a full commit SHA
.github/workflows/release.yml uses changesets/action@v1, which is a mutable tag; pin to an immutable commit SHA to avoid supply-chain changes to the action code.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 38-38: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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/release.yml at line 38, Replace the mutable tag uses:
changesets/action@v1 with an immutable pinned commit SHA (e.g., uses:
changesets/action@<full-commit-sha>) so the workflow uses a specific commit;
update the single occurrence of "changesets/action@v1" in the release workflow
to the chosen full commit SHA and commit the change to ensure the action is not
using a mutable tag.
Source: Linters/SAST tools
| The project uses two GitHub Actions workflows for automated releases: | ||
|
|
||
| - When changesets are merged to `main` | ||
| - CI will automatically version and publish | ||
| - Releases are created automatically | ||
| - **`release.yml`**: Runs on every push to `main`. Uses Changesets to version packages and create git tags. When pending changesets exist, it opens a "Release Packages" PR. When merged, it bumps versions and pushes a `v*` tag. | ||
| - **`publish.yml`**: Triggers on `v*` tag pushes. Builds the project and publishes to both registries using OIDC, then creates the GitHub Release. | ||
|
|
There was a problem hiding this comment.
Align the manual release steps with the new two-workflow model.
The new automation description at Line 72-76 conflicts with existing manual commands at Line 55 and Line 167 (bun run release). Keeping both paths in one runbook can cause an operator to bypass the tag-driven publish.yml flow and produce partial/duplicate release behavior.
🤖 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 `@RELEASE.md` around lines 72 - 76, The RELEASE.md currently documents both the
new two-workflow automation (release.yml and publish.yml) and the old manual
command (`bun run release`), which conflicts and can cause duplicate/partial
releases; update RELEASE.md to remove or modify the manual `bun run release`
instructions (references to the command and any step at Line 55/167) so manual
release guidance explicitly matches the new model: either instruct operators to
open/merge the "Release Packages" PR (release.yml) or to create/push a semantic
`v*` git tag to trigger publish.yml, and ensure the doc mentions the "Release
Packages" PR, release.yml, publish.yml, and the v* tag as the unique triggers to
avoid bypassing publish.yml.
| - **`publish.yml`**: Triggers on `v*` tag pushes. Builds the project and publishes to both registries using OIDC, then creates the GitHub Release. | ||
|
|
There was a problem hiding this comment.
Fix authentication wording for registry publishing.
Line 75 says both registries publish “using OIDC”, but GitHub Packages publishing is done with GITHUB_TOKEN (not OIDC) in .github/workflows/publish.yml and is already documented at Line 106.
🤖 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 `@RELEASE.md` around lines 75 - 76, The RELEASE.md statement that both
registries publish "using OIDC" is incorrect; update the wording to reflect that
in publish.yml the external registry uses OIDC whereas GitHub Packages is
published using the GITHUB_TOKEN. Edit the sentence referencing publish.yml to
mention OIDC for the external registry and explicitly call out GITHUB_TOKEN for
GitHub Packages (and optionally reference the GITHUB_TOKEN usage already
documented elsewhere).
Summary
release.yml(changesets versioning + tagging) andpublish.yml(OIDC publishing + GitHub Release)NPM_TOKENwith OIDC-based Trusted Publisher for npmjs.org@frontal-labs/clitypes: ["node"]in tsconfig, Zod.default()type mismatches)AbortControllermock using a class expression for propernewconstructabilityTest plan
tsc --noEmit)publish.ymlPost-merge setup
The npm Trusted Publisher must be configured at https://www.npmjs.com/package/frontal-cli/settings/trusted-publishers with owner
frontal-labs, repocli, workflowpublish.yml. TheNPM_TOKENsecret can be removed after.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores