Skip to content

ci: split release into changesets and OIDC publish workflows, fix type errors#12

Open
gabrielvfonseca wants to merge 9 commits into
mainfrom
refactor/drop-v2-unify-structure
Open

ci: split release into changesets and OIDC publish workflows, fix type errors#12
gabrielvfonseca wants to merge 9 commits into
mainfrom
refactor/drop-v2-unify-structure

Conversation

@gabrielvfonseca

@gabrielvfonseca gabrielvfonseca commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Splits the release pipeline into two workflows: release.yml (changesets versioning + tagging) and publish.yml (OIDC publishing + GitHub Release)
  • Replaces NPM_TOKEN with OIDC-based Trusted Publisher for npmjs.org
  • Adds publishing to GitHub Packages as @frontal-labs/cli
  • Fixes TypeScript type errors (missing types: ["node"] in tsconfig, Zod .default() type mismatches)
  • Fixes AbortController mock using a class expression for proper new constructability

Test plan

  • Type-check passes (tsc --noEmit)
  • All 51 tests pass
  • Linter clean on changed files
  • Merge a changeset PR to verify the release → publish flow end-to-end
  • Confirm Trusted Publisher is configured on npmjs.org for publish.yml

Post-merge setup

The npm Trusted Publisher must be configured at https://www.npmjs.com/package/frontal-cli/settings/trusted-publishers with owner frontal-labs, repo cli, workflow publish.yml. The NPM_TOKEN secret can be removed after.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Incident.io status-page integration for automated incident creation/updates/resolution
    • Synthetic monitoring with periodic E2E CLI journeys and per-journey reporting
    • Flap-prevention state machine to reduce alert noise and control create/update/resolve actions
  • Documentation

    • Added comprehensive on-call runbook for monitoring and recovery
    • Updated branding and install guidance to "Frontal"
  • Chores

    • Bumped package version and updated packaging metadata
    • Removed legacy Homebrew formula and some older workflows
    • Added monitoring/threshold configuration and publishing workflow

gabrielvfonseca and others added 6 commits June 7, 2026 12:45
- 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]>
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-io

snyk-io Bot commented Jun 7, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
🔚 Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This 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 frontal-labs/cli namespace.

Changes

Incident.io Monitoring & Branding Consolidation

Layer / File(s) Summary
Monitoring configuration and incident schema
.env.example, .github/incident/components.json, .github/incident/thresholds.json
Environment template and incident JSON configs define required API keys, status page IDs, severity IDs, alert thresholds, per-severity failed-journey limits, and timeout configuration.
E2E journey testing runner
.github/scripts/e2e-journey.sh
Runner executes six isolated CLI checks (smoke/auth/config/api/output/interactive) with per-journey timeout, output capture, and status mapping; aggregates results into run-result.json with per-journey metadata and computed overall summary.
Flap prevention state machine
.github/scripts/flap-state.sh
State script manages consecutive failure/success counters, applies hysteresis thresholds, computes create/update/resolve/no-op actions, maintains 96-entry event history, and exports action/counters/alerting flags for workflows.
Incident.io REST API client
.github/scripts/incident-client.sh
Client provides environment-driven Incident.io v2 API access with exponential backoff/jitter retry for 429/5xx, commands for internal/status-page incident lifecycle (create/update/resolve), status queries, and severity derivation from failed-journey counts.
Synthetic monitoring workflow (run-checks + manage-incidents)
.github/workflows/synthetic-monitor.yml
Scheduled 15-minute monitoring runs E2E journeys, updates flap state, derives severity tiers from failed-journey counts, conditionally creates/updates/resolves incidents and GitHub issues based on flap state.
Incident manager workflow (manual/programmatic actions)
.github/workflows/incident-manager.yml
Supports workflow_dispatch and workflow_call inputs for force-create (manual incident creation with severity), force-update/force-resolve (manual incident edits), auto (evaluate recommended action), and status-check (report current state).
Flap state cleanup workflow (daily maintenance)
.github/workflows/flap-state-cleanup.yml
Daily cron validates and trims flap-state.json history to 96 entries, detects stale Incident.io incidents (HTTP 404), and resets alerting when state/incident sync diverges.
Status page sync workflow (hourly reconciliation)
.github/workflows/status-page-sync.yml
Hourly cron fetches status page structure, cross-references flap state context, lists active incidents filtered by name, publishes component-status.json artifact with reconciliation snapshot.
Validate status page integration workflow
.github/workflows/validate-status-page.yml
PR validation checks config schema via jq, runs dry-run E2E monitoring, exercises five flap-state hysteresis scenarios, verifies API connectivity and required secrets/variables, gates all checks.
Operational runbook and incident guide
.github/ISOLATED_CONTEXT.md
Comprehensive on-call documentation with system overview, key files, secrets/variable reference, step-by-step recovery procedures (flapping, stale state, API outages), hysteresis behavior rules, and validation guidance.
Repository naming, package metadata, and branding updates
CONTRIBUTING.md, README.md, docker-compose.yml, package.json, docs/*, tests/snapshots/*, .github/workflows/codeql.yml
Updates clone URLs to frontal-labs/cli, renames docker-compose service from frontal-cli to frontal, bumps package version to 0.1.2, replaces "Frontal platform" wording with "Frontal" throughout docs, removes CodeQL workflow and legacy Homebrew documentation.

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A bunny watched the status page blink,
With flaps and thresholds, cause and link—
Journeys run, the state counts true,
Incidents managed, the alerts undo—
Branding polished, logs anew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.63% 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
Title check ✅ Passed The title clearly summarizes the main changes: splitting release workflows, switching to OIDC publishing, and fixing TypeScript errors.
Description check ✅ Passed The description is comprehensive, covering the split release pipeline, OIDC switch, GitHub Packages publishing, type fixes, test plan, and post-merge setup requirements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/drop-v2-unify-structure
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch refactor/drop-v2-unify-structure

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

@gabrielvfonseca gabrielvfonseca self-assigned this Jun 7, 2026
@greptile-apps

greptile-apps Bot commented Jun 7, 2026

Copy link
Copy Markdown

Greptile Summary

This 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.

  • Synthetic monitor: Runs six E2E CLI journeys every 15 minutes, accumulates consecutive failure/success counts, and drives create/update/resolve actions against Incident.io only after thresholds are crossed.
  • Incident lifecycle scripts (e2e-journey.sh, flap-state.sh, incident-client.sh): Manage journey execution, state persistence via GitHub Actions cache, and API calls with retry/backoff logic.
  • Supporting workflows: validate-status-page.yml for PR gating, status-page-sync.yml for hourly reconciliation, and flap-state-cleanup.yml for daily stale-state housekeeping.

Confidence Score: 4/5

Safe 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 issues: write, incident ID writeback omission, missing internal resolve, and the auto mode stdout/file confusion) are addressed in this revision. The two remaining findings are both display-level or configuration gaps: incident messages for singleton journeys render null for the journey name due to a .key vs .name mismatch in a jq expression, and the flap-state-cleanup.yml stale-incident check is silently skipped every run because the API key env var is never wired into the step. Neither breaks incident creation, the flap state machine, or any existing tests.

synthetic-monitor.yml (failure-list jq expression) and flap-state-cleanup.yml (missing API key injection on the Clean state step).

Important Files Changed

Filename Overview
.github/workflows/synthetic-monitor.yml Core monitoring workflow — previous blocking issues (static cache key, missing issues: write, incident ID writeback, internal resolve) are all fixed. One display bug remains in the failure-list jq expression for singleton journeys.
.github/scripts/incident-client.sh Incident.io v2 API client with retry/backoff; cmd_resolve_internal, notify_subscribers, and dry-run paths all look correct.
.github/scripts/flap-state.sh Hysteresis state machine is well-implemented; init/update/evaluate/resolve commands all behave correctly, history is trimmed to 96 entries, and GITHUB_OUTPUT writes are correct.
.github/scripts/e2e-journey.sh Journey runner is solid; compute_summary correctly inherits parent locals via forked subshell.
.github/workflows/incident-manager.yml Manual/auto incident management workflow; auto mode now correctly reads flap-state.sh stdout. Force-create correctly patches IDs and alerting flag back into state before saving cache.
.github/workflows/validate-status-page.yml PR gate workflow; dynamic-needs access bug is resolved by inlining result names.
.github/workflows/flap-state-cleanup.yml Daily housekeeping workflow; the stale-incident verification branch is always skipped because INCIDENT_IO_API_KEY is never injected into the Clean state step.
.github/workflows/status-page-sync.yml Hourly reconciliation workflow; reads flap state and cross-references active SP incidents. Read-only — no mutations, so low risk.
.github/incident/components.json Placeholder component config — contains a stub component_id that must be replaced with real Incident.io IDs before production use.

Sequence Diagram

sequenceDiagram
    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})"
Loading

Reviews (4): Last reviewed commit: "ci: split release into changesets and OI..." | Re-trigger Greptile

Comment thread .github/workflows/synthetic-monitor.yml
Comment thread .github/workflows/synthetic-monitor.yml
Comment thread .github/workflows/synthetic-monitor.yml Outdated
Comment thread .github/workflows/synthetic-monitor.yml
Comment thread .github/workflows/validate-status-page.yml
Comment thread .github/scripts/incident-client.sh Outdated
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]>
Comment thread .github/workflows/synthetic-monitor.yml Outdated
Comment thread .github/workflows/synthetic-monitor.yml
Comment thread .github/workflows/incident-manager.yml

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

Tighten 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 win

Align repo root naming across the same doc.

Line 30 now uses cd cli, but the project tree still starts with frontal-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 win

Clarify that the API job is conditional, not always required.

This section currently reads as “all 5 jobs must pass,” but api-connectivity is optional (test_api input). 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 win

Add 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/step env: 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 -->

Comment thread .github/incident/components.json Outdated
Comment thread .github/incident/thresholds.json Outdated
Comment thread .github/scripts/e2e-journey.sh
Comment thread .github/scripts/flap-state.sh
Comment thread .github/scripts/incident-client.sh
Comment thread .github/workflows/validate-status-page.yml
Comment thread .github/workflows/validate-status-page.yml Outdated
Comment thread .github/workflows/validate-status-page.yml
Comment thread .github/workflows/validate-status-page.yml Outdated
Comment thread RELEASE.md Outdated
… 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]>

@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

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 | 🔴 Critical

Guard incident-client.sh dispatcher when the script is sourced
Both .github/workflows/synthetic-monitor.yml and .github/workflows/incident-manager.yml use source .github/scripts/incident-client.sh and then call cmd_*, but .github/scripts/incident-client.sh runs an unguarded case "${1:-}" in ... esac on load; when sourced with no subcommand args it hits the *) usage branch and exit 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 | 🟠 Major

Make the run-result fallback reachable when the artifact is missing

actions/download-artifact@v4 fails the step when the named artifact isn’t found (“No artifacts found …”), so the subsequent step that writes run-result.json won’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 | 🟠 Major

Resolve 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 the SP_ID check. If flap-state.json has only internal_incident_id (or status_page_incident_id is null/missing), the workflow skips cmd_resolve_internal but 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 | 🟠 Major

Auto mode should fail fast instead of defaulting flap-state.sh evaluate errors to no-op.

The auto-mode step doesn’t enable set -euo pipefail and extracts ACTION via a pipeline (... | grep ... | cut ...), so a failing flap-state.sh evaluate (e.g., missing jq) won’t stop the workflow and will result in an empty ACTION that falls back to no-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 | 🟠 Major

Fix flap-state.json cache persistence on workflow reruns (cache key uses only github.run_id).

On re-runs, github.run_id stays the same, so the restore step rehydrates the previous attempt’s flap-state.json, and actions/cache/save@v4 cannot overwrite the already-saved cache entry for that primary key—so the corrected state won’t persist for subsequent force-create/force-resolve attempts.

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 | 🟠 Major

Cleanup reruns must include github.run_attempt in the cache key

With github.run_id alone, a rerun restores the prior attempt’s flap-state.json, and actions/cache/save@v4 will 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 win

Make api-connectivity actually fail the gate when the explicit API test fails.

Adding api-connectivity to needs is ineffective right now because that job still exits successfully on 401 and other non-200 responses. When test_api is 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-check still doesn't enforce the required pre-merge config.

This step now reads the right env values, 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_dispatch or 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 | 🟠 Major

Use github.run_attempt in the flap-state cache key.

github.run_id stays constant across re-runs, so the cache restore loads the previous attempt’s flap-state.json, and actions/cache/save@v4 can’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

📥 Commits

Reviewing files that changed from the base of the PR and between f95472a and b1f81f2.

📒 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

Comment on lines +47 to +50
- name: Install Frontal CLI
run: |
VERSION="${{ inputs.cli_version || 'latest' }}"
npm install -g "frontal-cli@${VERSION}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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

@gabrielvfonseca gabrielvfonseca changed the title feat: add automated Incident.io status page integration ci: split release into changesets and OIDC publish workflows, fix type errors Jun 7, 2026

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

📥 Commits

Reviewing files that changed from the base of the PR and between b1f81f2 and f7dca6e.

📒 Files selected for processing (7)
  • .github/workflows/publish.yml
  • .github/workflows/release.yml
  • RELEASE.md
  • src/config/schema.ts
  • src/schemas/frontal-json.ts
  • tests/utils/mocks.ts
  • tsconfig.json

Comment on lines +17 to +23
- name: Checkout
uses: actions/checkout@v4

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
ls -la .github/workflows || true
sed -n '1,120p' .github/workflows/publish.yml

Repository: frontal-labs/cli

Length of output: 3429


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "actions/checkout@|persist-credentials" -S .github/workflows || true

Repository: frontal-labs/cli

Length of output: 1352


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,160p' .github/workflows/publish.yml

Repository: frontal-labs/cli

Length of output: 2700


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "persist-credentials|actions/checkout@" .github/workflows -S

Repository: frontal-labs/cli

Length of output: 1352


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,200p' .github/workflows/publish.yml

Repository: frontal-labs/cli

Length of output: 2700


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "persist-credentials|actions/checkout@" .github/workflows -S

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


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "persist-credentials" -S .github/workflows || true

Repository: frontal-labs/cli

Length of output: 42


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "persist-credentials" -S .github/workflows || true

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


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "persist-credentials" -S .github/workflows || true

Repository: 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.

Suggested change
- 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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" || true

Repository: 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" || true

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


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


Pin GitHub Actions to immutable commit SHAs (and disable checkout credential persistence).

  • Line 18 uses actions/checkout@v4, Line 21 uses oven-sh/setup-bun@v2, and Line 63 uses softprops/action-gh-release@v2 — replace each with a full 40-char commit SHA.
  • Line 18 does not set with: persist-credentials: false; default persist-credentials: true configures 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 || true

Repository: 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 || true

Repository: 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 || true

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

Comment thread RELEASE.md
Comment on lines +72 to +76
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment thread RELEASE.md
Comment on lines +75 to +76
- **`publish.yml`**: Triggers on `v*` tag pushes. Builds the project and publishes to both registries using OIDC, then creates the GitHub Release.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant