diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 524454a..7b99be3 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "ccmagic", "source": "./", "description": "25 dev workflow skills: tracker-aware ticket lifecycle (Linear, GitHub, JIRA) with an autonomous end-to-end driver, adaptive code review, debugging, design/QA, and git workflow utilities", - "version": "3.20.3", + "version": "3.20.4", "author": { "name": "Devon Hillard" }, diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 1c42db5..c197503 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "ccmagic", "description": "Dev workflow skills for Claude Code: tracker-aware ticket lifecycle (Linear, GitHub, JIRA), adaptive code review, debugging, design/QA, and git workflow utilities", - "version": "3.20.3", + "version": "3.20.4", "author": { "name": "Devon Hillard", "url": "https://github.com/devondragon" }, "repository": "https://github.com/devondragon/ccmagic", "license": "Apache-2.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index c446689..0fa1ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to ccmagic are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.20.4] - 2026-10 + +### Fixed + +- `ccm-ci-status` reports a new status, `conflicting` (exit 6), when nothing has registered for the head commit and the PR conflicts with its base. GitHub runs no `pull_request` workflows on a conflicting PR, so `--watch` used to wait out the whole `ci_timeout_minutes` and `auto-ticket` then parked with "CI never registered", which named the symptom and not the cause (AIE-351). `auto-ticket` now stops at once with the conflict as the reason, and `finish-ticket` and `merge` treat it as the conflict blocker the merge gate already reports. + ## [3.20.3] - 2026-10 ### Fixed diff --git a/README.md b/README.md index 14e8902..d516765 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ Rules that have one right answer are enforced in code, not left to the skill tex | Script | Answers | |---|---| | `ccm-context` | Current branch, ticket ID parsed from it, open PR, worktree or primary checkout, base branch, and the resolved `ccmagic.local.md` config (project over user over defaults) | -| `ccm-ci-status` | Is CI green for this PR: `green`, `no-ci`, `failed`, `pending`, `not-registered`, `unreadable`. Falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs. `--watch --wait-key K` waits with a deadline that holds across repeated calls | +| `ccm-ci-status` | Is CI green for this PR: `green`, `no-ci`, `failed`, `pending`, `not-registered`, `conflicting`, `unreadable`. Falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs. `--watch --wait-key K` waits with a deadline that holds across repeated calls | | `ccm-merge-gate` | May this PR merge: open, mergeable, CI green (or no CI), no reviewer whose latest review requests changes | | `ccm-pr-threads` | A PR's review threads (grouped, with an `open` flag), reviews, and conversation comments; `--since-id H` marks reviewer comments newer than a high-water mark. A thread is handled when resolved, or when the author's last reply carries a `ccm-pr-reply` disposition marker; a `fixed` marker counts only once its commit is verified on the branch and touches the thread's file | | `ccm-validate` | Do the project's checks pass: runs `format`, `lint`, `types`, `test`, and `build`, one command each from a `validate_` config key or detected from `package.json`, `Makefile`, Gradle, Maven, `go.mod`, `Cargo.toml`, or `pyproject.toml`, and uses each command's exit code as the verdict (`pass`, `fail`, `nothing-to-run`). `--only` runs a subset and `--list` shows the commands without running them. Each check is bounded by `validate_timeout_seconds` (default 540, at most 7200). For checks longer than one 10-minute Bash call, `--start` runs them in a detached process, or `--start --attached` only prepares the run (writing its state with no result, so a `--wait` never reads an earlier run's) and `--attach RUN` then runs its worker in the foreground for a harness background task to own (a sandbox that ends a call's processes kills a detached run), and `--wait [SECONDS]` (default 480) waits for it by its heartbeat, printing the plain result when it finishes or `status: running` with exit 6 while it goes on; `/ccmagic:validate` prepares with `--start --attached` in an ordinary Bash call and runs `--attach` as a background Bash call when `validate_timeout_seconds` is above 540. A Gradle or Maven build (run through `./gradlew` or `./mvnw` when present) supplies `test` and `build` ahead of `package.json` and the `Makefile`, which still supply `format`, `lint`, and `types`. When the root `package.json` declares dependencies, a package under `dependencies` or `devDependencies` is not in `node_modules` (a bare `node_modules` directory does not count), and a check that will run goes through Node (a `package.json` script, or a command with `npm`, `npx`, `pnpm`, `yarn`, `bun`, `bunx`, or `node` as a command word), the first check is preceded by the install its lockfile calls for (`npm ci`, or `pnpm`, `yarn`, or `bun` when that tool is installed), and `--install` runs only that; with no lockfile, a missing tool, or a failed install, no check runs and the status is `environment`. Node only: no other ecosystem's dependencies are installed | diff --git a/bin/ccm-ci-status b/bin/ccm-ci-status index 1da0077..0022b1c 100755 --- a/bin/ccm-ci-status +++ b/bin/ccm-ci-status @@ -12,6 +12,8 @@ # failed 1 at least one check failed, was cancelled, or timed out # pending 2 checks still running # not-registered 2 CI is configured but no check or run exists for this commit yet +# conflicting 6 nothing has registered and the PR conflicts with its base; +# GitHub runs no pull_request workflows until that is resolved # unreadable 3 neither the Checks API nor the Actions/Status APIs could be read # timeout 5 --watch deadline passed while still pending/not-registered # (usage error 4) @@ -24,6 +26,10 @@ # (403) and nothing else is found, `no-ci` needs branch protection to be # readable and show no required checks; otherwise the status is `unreadable`. # +# Before reporting not-registered for a PR, it re-reads the PR's mergeability: +# GitHub cannot build the merge ref for a CONFLICTING PR, so its pull_request +# workflows never start, and waiting out the timeout would only hide that. +# # --watch polls until the status leaves pending/not-registered. One call never # runs longer than --max-call-seconds (default 540, under the Bash tool's # 10-minute limit). --wait-key names a deadline file under the git dir, so the @@ -43,7 +49,7 @@ while [ $# -gt 0 ]; do --timeout-minutes) timeout_min=${2:?}; shift ;; --interval) interval=${2:?}; shift ;; --max-call-seconds) max_call=${2:?}; shift ;; - -h|--help) sed -n '2,31p' "$0"; exit 0 ;; + -h|--help) sed -n '2,38p' "$0"; exit 0 ;; -*) echo "ccm-ci-status: unknown flag $1" >&2; exit 4 ;; *) pr=${1#\#} ;; esac @@ -109,6 +115,10 @@ no_checks_verdict() { result no-ci none "no workflow files and no required status checks on $base" else code=$([ "$has_workflows" = true ] && echo "workflow files exist" || echo "required status checks exist") + if [ -n "$pr" ] && [ "$(gh pr view "$pr" --json mergeable -q .mergeable 2>/dev/null || true)" = CONFLICTING ]; then + result conflicting none "PR #$pr has merge conflicts with $base, so GitHub will not run its pull_request workflows; nothing has registered for $sha ($code)" + return + fi result not-registered none "CI is configured ($code) but nothing has registered for $sha yet" fi } @@ -194,6 +204,7 @@ exit_for() { pending|not-registered) echo 2 ;; unreadable) echo 3 ;; timeout) echo 5 ;; + conflicting) echo 6 ;; *) echo 3 ;; esac } diff --git a/skills/auto-ticket/SKILL.md b/skills/auto-ticket/SKILL.md index 88bae1c..3b12fb9 100644 --- a/skills/auto-ticket/SKILL.md +++ b/skills/auto-ticket/SKILL.md @@ -196,6 +196,7 @@ Everything this pass pushes is measured against `H`, so bot reviews triggered *b - `green` or `no-ci` → CI is settled and green (record "no CI configured" in the run summary for `no-ci`). - `failed` → settled, not green. Carry `reason` and `checks` into 4d. - `timeout` → **route-and-stop** (reason: "CI did not complete within the timeout", plus the script's `reason`; a `not-registered` timeout means CI is configured but no run appeared for the head SHA). + - `conflicting` → **route-and-stop** (reason: "PR has merge conflicts with its base, so CI cannot run: {reason}"). GitHub runs no `pull_request` workflows on a conflicting PR, so there is nothing to wait for; the conflicts need resolving first. - `unreadable` → **route-and-stop** (reason: "cannot read CI status: {reason}"). The script handles what this step used to spell out: the fine-grained-PAT 403 fallback to the Actions and commit-status APIs, the "no checks at all" case (no workflows and no required checks means `no-ci`; otherwise it keeps waiting for a run to register), and quick transient `gh` failures. Don't re-derive CI state from `gh pr checks` output. diff --git a/skills/finish-ticket/SKILL.md b/skills/finish-ticket/SKILL.md index d7cdbd4..b766e6b 100644 --- a/skills/finish-ticket/SKILL.md +++ b/skills/finish-ticket/SKILL.md @@ -133,7 +133,7 @@ It exits 0 when the PR may merge and 1 when it may not, and prints JSON: - `blockers`: each reason the PR can't merge. It covers: the PR isn't `OPEN`; it is `CONFLICTING`, or GitHub still reports `UNKNOWN` mergeability after a few re-reads; CI isn't green; a reviewer's latest review is `CHANGES_REQUESTED`. - `warnings`: currently only "no approving review". A PR can proceed without an approval; show the warning. -- `ci`: the full `ccm-ci-status` result. `ci.status` is `green`, `no-ci` (no workflows and no required checks), `failed`, `pending`, `not-registered` (CI configured but nothing has reported for this commit yet), or `unreadable`. `ci.checks` lists each check with its bucket, for the report. The script already falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs, so a 403 is never on its own a reason to call CI failed or to ask anyone to look at the checks. +- `ci`: the full `ccm-ci-status` result. `ci.status` is `green`, `no-ci` (no workflows and no required checks), `failed`, `pending`, `not-registered` (CI configured but nothing has reported for this commit yet), `conflicting` (nothing has reported and the PR conflicts with its base, so GitHub will not run its `pull_request` workflows; the conflict is already a blocker, so do not wait), or `unreadable`. `ci.checks` lists each check with its bucket, for the report. The script already falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs, so a 403 is never on its own a reason to call CI failed or to ask anyone to look at the checks. If `ci.status` is `pending` or `not-registered`, wait for CI rather than reporting it as a blocker yet: diff --git a/skills/merge/SKILL.md b/skills/merge/SKILL.md index d77cf38..c3399e9 100644 --- a/skills/merge/SKILL.md +++ b/skills/merge/SKILL.md @@ -51,7 +51,7 @@ fi "${CLAUDE_PLUGIN_ROOT}/bin/ccm-merge-gate" [PR-NUMBER] ``` -It exits 0 when the PR may merge and prints JSON with `blockers` (not open, conflicting or unknown mergeability, CI not green, a reviewer's latest review is `CHANGES_REQUESTED`), `warnings` (no approving review), and the full CI result under `ci`. Report the blockers and warnings as given; do not re-derive them from `gh pr view`. If `ci.status` is `pending` or `not-registered`, wait with `"${CLAUDE_PLUGIN_ROOT}/bin/ccm-ci-status" [PR-NUMBER] --watch --wait-key merge-[PR-NUMBER]` (repeat while it returns `"call_again": true`), then run the gate again. +It exits 0 when the PR may merge and prints JSON with `blockers` (not open, conflicting or unknown mergeability, CI not green, a reviewer's latest review is `CHANGES_REQUESTED`), `warnings` (no approving review), and the full CI result under `ci`. Report the blockers and warnings as given; do not re-derive them from `gh pr view`. A `ci.status` of `conflicting` means CI cannot start until the conflicts are resolved; do not wait for it. If `ci.status` is `pending` or `not-registered`, wait with `"${CLAUDE_PLUGIN_ROOT}/bin/ccm-ci-status" [PR-NUMBER] --watch --wait-key merge-[PR-NUMBER]` (repeat while it returns `"call_again": true`), then run the gate again. If the gate fails, show the blockers and stop. Merge anyway only if the user explicitly says to. When `merge_guard: on` is set, the PreToolUse hook denies the merge command while the gate fails; after the user's explicit choice, prefix the merge command with `CCMAGIC_MERGE_OVERRIDE=1`. diff --git a/tests/every-ticket-since-baseline.diff b/tests/every-ticket-since-baseline.diff index 40e5fb6..ff91c30 100644 --- a/tests/every-ticket-since-baseline.diff +++ b/tests/every-ticket-since-baseline.diff @@ -214,7 +214,15 @@ **4c. Wait for CI and new reviews.** This pass's push(es) already happened (in 4a via `pr-feedback`, and possibly again in 4b); `H` was captured before them at the top of the pass. Now that they've landed: - **Wait for CI to settle** with one command, repeated only while it says so: -@@ -198,11 +214,13 @@ Each 4f follows a feedback pass that pushed, so a run makes at most `max_feedbac +@@ -166,6 +182,7 @@ Everything this pass pushes is measured against `H`, so bot reviews triggered *b + - `green` or `no-ci` → CI is settled and green (record "no CI configured" in the run summary for `no-ci`). + - `failed` → settled, not green. Carry `reason` and `checks` into 4d. + - `timeout` → **route-and-stop** (reason: "CI did not complete within the timeout", plus the script's `reason`; a `not-registered` timeout means CI is configured but no run appeared for the head SHA). ++ - `conflicting` → **route-and-stop** (reason: "PR has merge conflicts with its base, so CI cannot run: {reason}"). GitHub runs no `pull_request` workflows on a conflicting PR, so there is nothing to wait for; the conflicts need resolving first. + - `unreadable` → **route-and-stop** (reason: "cannot read CI status: {reason}"). + + The script handles what this step used to spell out: the fine-grained-PAT 403 fallback to the Actions and commit-status APIs, the "no checks at all" case (no workflows and no required checks means `no-ci`; otherwise it keeps waiting for a run to register), and quick transient `gh` failures. Don't re-derive CI state from `gh pr checks` output. +@@ -198,11 +215,13 @@ Each 4f follows a feedback pass that pushed, so a run makes at most `max_feedbac Only 4e (with the PR head still at `reviewed_head`) and 4f (with a clean re-review of the PR head) lead here, so the last clean review read the commit the gate will merge. Never run this step while the PR head differs from `reviewed_head`: that is a stale review, which Reeve parks as `review-not-clean`; route-and-stop instead (stage = `review-ticket`). @@ -230,7 +238,7 @@ --- -@@ -210,10 +228,13 @@ Run the finish-ticket step via `run_step` — `/ccmagic:finish-ticket` with the +@@ -210,10 +229,13 @@ Run the finish-ticket step via `run_step` — `/ccmagic:finish-ticket` with the Whatever the outcome, record it on the PR and the ticket so the unattended run leaves an audit trail. @@ -247,7 +255,7 @@ Under prompt-relay there is no create API, so list every item under "to file" for a human. Every item appears in the summary's Follow-ups section; "none" is correct only when the list is empty. A parked run does the same before route-and-stop posts its note. -@@ -243,6 +264,15 @@ Under prompt-relay there is no create API, so list every item under "to file" fo +@@ -243,6 +265,15 @@ Under prompt-relay there is no create API, so list every item under "to file" fo - To file (prompt-relay): {item} (from {step}) - {or "none" when no step reported a follow-up} @@ -263,7 +271,7 @@ ### If parked **Waiting on:** {the reason}. Nothing was merged. Resolve it, then re-run `/ccmagic:auto-ticket {TICKET-ID}`. -@@ -252,6 +282,10 @@ Under prompt-relay there is no create API, so list every item under "to file" fo +@@ -252,6 +283,10 @@ Under prompt-relay there is no create API, so list every item under "to file" fo ``` ```` @@ -274,7 +282,7 @@ `steps` lists every sub-skill invocation of this run in order, one entry each, with the handshake `status` it returned; include `reason` whenever the sub-skill emitted one. Repeated review passes are separate entries, and each fix pass is a `work-ticket` entry whose `reason` starts with `fix pass {n} ({fix_source})`. `review_passes` is the total number of review-ticket calls in the run, 4f re-reviews included: the final `review_pass`, or 1 when there was no re-review. `follow_ups` has one entry per item on the run's follow-up list (empty array when there were none): `ticket` is the filed ID or null, and `reason` says why it wasn't filed (null when filed). The block is emitted on every outcome and under every transport. It is the machine-readable record an external gate reads, so its keys are fixed and must not be renamed; `follow_ups` was added in 3.13.0 as an additive key, and readers that don't know it ignore it. `version: 2` binds the record to what it describes, and Reeve treats a record whose binding doesn't match the PR it located as no record at all (RV-1). A version 2 record carries `repo`, `pr`, and `head_sha` on every outcome, never omitted (the one exception is the version 1 fallback below): -@@ -311,8 +345,12 @@ There is no stalled outcome. Never hang waiting for input. +@@ -311,8 +346,12 @@ There is no stalled outcome. Never hang waiting for input. | A sub-skill emits no handshake (crash/tool error) | Treat as `needs-human` → route-and-stop with reason "{skill} produced no handshake". | | `work-ticket` → `needs-human` | route-and-stop (stage `work-ticket`). | | `review-ticket` → `needs-human`, or `fixable-findings` unresolved after the bounded loop | route-and-stop (stage `review-ticket`). | @@ -289,7 +297,7 @@ | `pr-feedback` → `needs-human` (genuine reviewer tie) | route-and-stop (stage `pr-feedback`). | | Local `/ccmagic:validate` can't be made green within the bounded attempts | route-and-stop (reason: validation failures). | | CI never settles within `ci_timeout_minutes` (watch loop cap) | route-and-stop (reason: CI timeout). | -@@ -320,7 +358,7 @@ There is no stalled outcome. Never hang waiting for input. +@@ -320,7 +359,7 @@ There is no stalled outcome. Never hang waiting for input. | Step 4 pushed after the last clean review (4a or 4b) | Re-review the pushed head (4f). A stale review never reaches finish: a clean re-review of another head, an unreadable PR head, or findings with no review fix or feedback passes left → route-and-stop (stage `review-ticket`), before any fix is pushed. | | `finish-ticket` merge gate not satisfied | It returns `needs-human` and does not merge → route-and-stop (stage `finish-ticket`). | | `requested_state: {merge_handoff_state}` and no state of that exact name exists | route-and-stop (stage `finish-ticket`); nothing was merged on the hand-off path. | @@ -463,6 +471,15 @@ -If invoked as `/ccmagic:doctor --quick`, run `ccm-doctor --quick` (Project setup and Configuration only) and skip step 2. +If invoked as `/ccmagic:doctor --quick`, run `"${CLAUDE_PLUGIN_ROOT}/bin/ccm-doctor" --quick` (Project setup and Configuration only) and skip step 2. ### skills/finish-ticket/SKILL.md +@@ -133,7 +133,7 @@ It exits 0 when the PR may merge and 1 when it may not, and prints JSON: + + - `blockers`: each reason the PR can't merge. It covers: the PR isn't `OPEN`; it is `CONFLICTING`, or GitHub still reports `UNKNOWN` mergeability after a few re-reads; CI isn't green; a reviewer's latest review is `CHANGES_REQUESTED`. + - `warnings`: currently only "no approving review". A PR can proceed without an approval; show the warning. +-- `ci`: the full `ccm-ci-status` result. `ci.status` is `green`, `no-ci` (no workflows and no required checks), `failed`, `pending`, `not-registered` (CI configured but nothing has reported for this commit yet), or `unreadable`. `ci.checks` lists each check with its bucket, for the report. The script already falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs, so a 403 is never on its own a reason to call CI failed or to ask anyone to look at the checks. ++- `ci`: the full `ccm-ci-status` result. `ci.status` is `green`, `no-ci` (no workflows and no required checks), `failed`, `pending`, `not-registered` (CI configured but nothing has reported for this commit yet), `conflicting` (nothing has reported and the PR conflicts with its base, so GitHub will not run its `pull_request` workflows; the conflict is already a blocker, so do not wait), or `unreadable`. `ci.checks` lists each check with its bucket, for the report. The script already falls back to the Actions and commit-status APIs when a fine-grained PAT gets HTTP 403 on check runs, so a 403 is never on its own a reason to call CI failed or to ask anyone to look at the checks. + + If `ci.status` is `pending` or `not-registered`, wait for CI rather than reporting it as a blocker yet: + @@ -433,7 +433,7 @@ Absent all three, run the interactive path exactly as documented above. Also rea ### Behavior at each human-gate @@ -486,6 +503,15 @@ - **Purpose:** Deep iterative research with parallel exploration, source evaluation, and confidence scoring. - **When to use:** Before designing a non-trivial feature, or to investigate a library/pattern. ### skills/merge/SKILL.md +@@ -51,7 +51,7 @@ fi + "${CLAUDE_PLUGIN_ROOT}/bin/ccm-merge-gate" [PR-NUMBER] + ``` + +-It exits 0 when the PR may merge and prints JSON with `blockers` (not open, conflicting or unknown mergeability, CI not green, a reviewer's latest review is `CHANGES_REQUESTED`), `warnings` (no approving review), and the full CI result under `ci`. Report the blockers and warnings as given; do not re-derive them from `gh pr view`. If `ci.status` is `pending` or `not-registered`, wait with `"${CLAUDE_PLUGIN_ROOT}/bin/ccm-ci-status" [PR-NUMBER] --watch --wait-key merge-[PR-NUMBER]` (repeat while it returns `"call_again": true`), then run the gate again. ++It exits 0 when the PR may merge and prints JSON with `blockers` (not open, conflicting or unknown mergeability, CI not green, a reviewer's latest review is `CHANGES_REQUESTED`), `warnings` (no approving review), and the full CI result under `ci`. Report the blockers and warnings as given; do not re-derive them from `gh pr view`. A `ci.status` of `conflicting` means CI cannot start until the conflicts are resolved; do not wait for it. If `ci.status` is `pending` or `not-registered`, wait with `"${CLAUDE_PLUGIN_ROOT}/bin/ccm-ci-status" [PR-NUMBER] --watch --wait-key merge-[PR-NUMBER]` (repeat while it returns `"call_again": true`), then run the gate again. + + If the gate fails, show the blockers and stop. Merge anyway only if the user explicitly says to. When `merge_guard: on` is set, the PreToolUse hook denies the merge command while the gate fails; after the user's explicit choice, prefix the merge command with `CCMAGIC_MERGE_OVERRIDE=1`. + @@ -98,7 +98,7 @@ gh pr merge --rebase --delete-branch ```markdown 1. Detect platform (GitHub/GitLab/Bitbucket) diff --git a/tests/run.sh b/tests/run.sh index 389e6c5..f500711 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -299,6 +299,36 @@ ci_not_registered_when_required_checks_exist() { check "$(jqval .status)" "not-registered" } +ci_conflicting_pr_is_not_waited_on() { + workflows + pr_open CONFLICTING + fx pr-checks '[]' + fx run-list '[]' + fx status '{"state":"pending","total_count":0,"statuses":[]}' + fx_err protection 'HTTP 404' 1 + run "$BIN/ccm-ci-status" 7 + check "$(jqval .status),$RC" "conflicting,6" +} + +ci_watch_stops_on_conflicting_pr() { + workflows + pr_open CONFLICTING + fx pr-checks '[]' + fx run-list '[]' + fx status '{"state":"pending","total_count":0,"statuses":[]}' + fx_err protection 'HTTP 404' 1 + run "$BIN/ccm-ci-status" 7 --watch --wait-key k --timeout-minutes 30 --interval 0 + check "$(jqval .status),$RC" "conflicting,6" + check "$(find "$(git rev-parse --git-dir)/ccmagic" -type f | wc -l | tr -d ' ')" "0" +} + +ci_conflicting_pr_with_checks_reports_checks() { + pr_open CONFLICTING + fx pr-checks '[{"name":"test","state":"SUCCESS","bucket":"pass"}]' + run "$BIN/ccm-ci-status" 7 + check "$(jqval .status)" "green" +} + ci_watch_times_out_on_shared_deadline() { pr_open fx pr-checks '[{"name":"test","state":"IN_PROGRESS","bucket":"pending"}]' 8