Skip to content

Add /crow-cascade-epic: run an epic as a dependency-gated cascade (CROW-1312) - #1313

Merged
dhilgaertner merged 5 commits into
mainfrom
feature/crow-1312-cascade-epic-skill
Oct 1, 2026
Merged

dhilgaertner merged 5 commits into
mainfrom
feature/crow-1312-cascade-epic-skill

Conversation

@dhilgaertner

Copy link
Copy Markdown
Contributor

Closes #1312

Summary

Adds /crow-cascade-epic <epic-url>, a new bundled Crow skill. It packages the dependency-gated epic cascade we have been running by hand (most recently the NAT-removal epic corveil-cloud-terraform#618 → T1–T8) into a repeatable skill:

  1. Read the epic. Children come from GitHub sub-issues, the epic's task list, and references. Dependency edges come from blocked_by links, Depends on lines in child bodies, and the epic's own wave list.
  2. Layer it into waves. cascade.sh plan runs Kahn's algorithm and rejects cycles, unknown dependencies, and missing fields. Against the real feat(web): skeleton-load the left pane before list-sessions #618 it reproduces the hand-built T1 · T2/T3 · T4/T5/T6 · T7 · T8.
  3. Launch the current wave, one Crow work session per ticket. It delegates to crow-workspace/setup.sh and does no setup of its own. Tickets are launched one at a time, and each launch is followed by a crow list-worktrees check (plus a re-check 5s later). This is the add-worktree registration lost under rapid session creation (setup.sh dies at launch_agent: "no registered worktree with a branch") #1301 constraint: Stop the store-reload poll from dropping just-registered worktrees (CROW-1301) #1308 fixed that race, but the skill can't tell which crowd build is running, and a wave is only a few tickets.
  4. Seed each coder with the cascade contract. One PR scoped to its ticket, with Closes #N and the default branch as base. It requests the human reviewer (--reviewer, default dgershman) and never self-approves. Then the gate rule:
    • --gate human: no crow:merge. The reviewer merges by hand.
    • --gate auto: crow add-merge-label, so Crow's auto-merge watcher merges on green.
    • Inferred when omitted: auto only if the base branch's ruleset requires an approving review and the auto-merge watcher is on. Otherwise human.
  5. Arm a wave watcher. cascade.sh watch runs as a background Bash task and exits 0 only when every ticket in the wave has a merged PR, or was closed as completed by a human. Exit 10 means re-arm; 3 means blocked (a ticket closed as not-planned).
  6. Resume on re-run. Progress is read live from GitHub (closedByPullRequestsReferences, plus the PR the coder registered on its session) and from Crow (list-sessions matched by ticket_url). A re-run lands on the right wave and never launches a ticket twice. The per-ticket contract comment carries a marker, so it posts only once.

status and watch also report contract drift on open PRs:

Changes

  • skills/crow-cascade-epic/SKILL.md: the orchestrator. It has flags --gate, --reviewer, --wave, --dry-run, --explore, and --no-comment. It includes the coder-prompt contract for both gates and the per-ticket comment, drift handling, explore mode, resume rules, and a worked example of the feat(web): skeleton-load the left pane before list-sessions #618 cascade from end to end.
  • skills/crow-cascade-epic/cascade.sh: the deterministic half, with plan, status, and watch. It is bash 3.2-safe and needs jq.
  • skills/crow-cascade-epic/cascade_test.sh: 61 checks against fake gh/crow. They cover wave layering and validation, every ticket state, every drift rule, the resume point / start_wave, and each watch exit code. It runs in make parity and in the CI parity job.
  • Resources/crow-cascade-epic-{SKILL.md,cascade.sh}.template: byte-identical twins for installed builds.
  • Scaffolder.swift: installs both files on every launch (the script as 0755), adds the two loaders, and adds the fallback allow rules.
  • settings.json: pre-approves cascade.sh, both the bash … and direct forms. Comment posting and PR edits still prompt.
  • scripts/check-workspace-custom-instructions.sh: frontmatter check, a byte-identity check across both halves of the skill and the script, and required policy text (both gates, reviewer, no self-approval, setup.sh delegation, sequential creation). The new diff check doesn't abort under pipefail before it reports.
  • LaunchScaffoldTests: expects the new files and asserts that both skill scripts are executable.
  • Docs: CLAUDE.md (and its template), docs/configuration.md, docs/getting-started.md, and CHANGELOG.md.

Testing

Limitations

  • Merge tracking reads GitHub. A Jira ticket whose code lands on GitHub works through the PR its coder registers on the session. GitLab MRs aren't tracked by cascade.sh yet; the skill says to check glab mr view and re-run.
  • Not exercised: a live launch of new sessions for a real epic. That would open sessions and post comments on real tickets, so I stopped at the read-only plan/status/watch run above.

🤖 Generated with Claude Code

…OW-1312)

A new bundled skill packages the cascade process we have been running by
hand (most recently corveil-cloud-terraform#618 → T1–T8). It reads an epic,
layers its sub-tickets into dependency waves, and launches one Crow work
session per ticket of the current wave through crow-workspace/setup.sh.
Launches run one at a time with a list-worktrees check (the #1301 race). A
background watcher releases the next wave only once every PR in the current
wave has merged.

Each coder prompt carries the cascade contract: one PR scoped to its
ticket with Closes #N, the human reviewer requested (default dgershman),
no self-approval, and the gate's label rule. --gate human leaves
crow:merge off and the reviewer merges by hand. --gate auto adds it via
crow add-merge-label. When the flag is omitted, auto is chosen only if
the base branch requires an approving review and the auto-merge watcher
is on.

cascade.sh is the deterministic half:
- plan: Kahn wave layering, cycle and unknown-dependency checks
- status: merge state from closedByPullRequestsReferences plus the
  session's registered PR, contract drift, and the resume point
- watch: exit 0 when the wave has merged, 10 to re-arm, 3 when blocked

Progress is always read live, so re-running the skill resumes without
launching a ticket twice. The Scaffolder installs the skill and script on
every launch, and settings.json pre-approves the script. The drift guard
keeps skills/ and Resources/ byte-identical and requires the policy text.
cascade_test.sh (61 checks, fake gh/crow) runs in make parity and CI.

🐦‍⬛ Generated with Claude Code, orchestrated by Crow

Co-Authored-By: Claude <[REDACTED_EMAIL]>
Crow-Session: 3D47A57F-2215-482F-879E-2593E521624F

@dgershman dgershman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code & Security Review

Critical Issues

Red — watch reports an open wave complete when jq is 1.6.

skills/crow-cascade-epic/cascade.sh uses jq 1.7 object shorthand ({id, label, …}) in the plan summary (cascade.sh:120) and in STATUS_JQ (cascade.sh:189, cascade.sh:231, cascade.sh:242). command -v jq on this machine is jq 1.6 (/usr/local/bin/jq), which fails to compile those filters, so status writes no JSON. cmd_watch then evaluates jq -e '.waves[0].complete' on that empty output (cascade.sh:346). jq 1.6 exits 0 for empty input, and the script takes the complete branch.

Reproduced by running bash skills/crow-cascade-epic/cascade_test.sh with that jq: 43 checks failed, and watch --wave 3 --interval 0 --timeout 0 exited 0 with WAVE 3 COMPLETE — that was the last wave while wave 3 was still open. The same suite is 61/61 under /usr/bin/jq 1.7.1-apple. CI can stay green because its jq is new enough; a Mac whose PATH selects Homebrew jq 1.6 will release the next wave without a merge. The Resources template is the same script.

Ship filters jq 1.6 can compile, or exit 2 from preflight when jq is older than 1.7. Also require a status object with "status":"ok" before the complete branch — on jq 1.6 the -e exit code alone does not mean the wave finished.

Architecture / Existing Patterns

  • Existing pathway: /crow-workspace's setup.sh. This skill sequences that script one ticket at a time (with the list-worktrees check) and leaves worktree creation, naming, and prompts on that path. That matches the #1301 registration race the skill is written around.

Security Review

Strengths:

  • cascade.sh only reads GitHub, through GraphQL variables, and Crow (list-sessions / list-links). Issue comments and gh pr edit stay off the pre-approved allow list.
  • An omitted --gate stays human unless a required approving review is visible and the auto-merge watcher is on.
  • Author self-approval is still flagged in the status JSON.

Concerns:

  • The Red finding is the one that breaks the gate: the wrong jq releases the next wave with nothing merged.
  • The Yellow finding drops the human-gate alert (crow:merge before a human approval) from the stream the Manager is told to act on.

Code Quality

Yellow — the watch stream never emits ⚠ contract: lines. cascade.sh:338-342 walks .drift[] and then reads .wave and .id off that string. Reproduced on jq 1.7 with an open PR that has crow:merge and no reviewer request: status listed both drift findings, and watch printed Cannot index string with string "id" plus the state line only, then exited 0. cascade_test.sh:224 asserts the state line and never a contract line, so the suite stays green. SKILL.md:249 tells the Manager to act on those lines, including removing a premature crow:merge. Capture the ticket ($t.wave, $t.id) before iterating drift, and assert a contract line in the watch test.

Green — a merged PR satisfies the ticket while another PR is still open. classify returns merged as soon as any linked PR is merged (cascade.sh:201), and the “more than one PR” drift counts only OPEN pulls. Reproduced: PR #11 merged and PR #12 still open with crow:merge left the wave complete: true. The state table treats that merge as enough; a drift line for a leftover open PR would be the tighter rule if a second PR should hold the wave.

swift build completed. cascade_test.sh is 61/61 on jq 1.7.1 and 18/61 on jq 1.6, with the false wave release above.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Recommendation: Request Changes — driven by 1 Red, 1 Yellow, 1 Green findings.


🐦‍⬛ Reviewed by Crow via Cursor

@dhilgaertner dhilgaertner added the crow:merge Crow auto-merge on green label Sep 30, 2026
Addresses the PR #1313 review.

Red: jq 1.6 treats `label` as a keyword, so the bare `{id, label, …}` keys
and `.label` reads failed to compile. `status` then printed nothing, and
jq 1.6's `-e` exits 0 on empty input, so `watch` released an unmerged wave.
- Quote the key everywhere (`"label": ."label"`, `$t."label"`). The suite
  now passes 77/77 under both jq 1.6 and 1.7.1 (it was 18/61 under 1.6).
- Fail closed. `status` emits an error object and exits 2 when its filter
  can't run. `watch` requires `"status":"ok"` before it trusts `complete`
  and exits 2 otherwise, so it never releases a wave it could not read.
- Preflight rejects jq older than 1.6.
- CI runs the suite a second time under a pinned, checksum-verified jq 1.6.

Yellow: the watch stream walked `.drift[]` and then read `.wave`/`.id`
off the string, so it never printed a `⚠ contract:` line. It now binds
the ticket first. A new test asserts the contract lines appear in the
stream.

Green: when a ticket already has a merged PR, another PR left open is
reported as drift. It doesn't hold the wave, because the ticket is done,
but the stray PR could still merge later.

🐦‍⬛ Generated with Claude Code, orchestrated by Crow

Co-Authored-By: Claude <[REDACTED_EMAIL]>
Crow-Session: 3D47A57F-2215-482F-879E-2593E521624F
@dhilgaertner

Copy link
Copy Markdown
Contributor Author

Thanks. All three findings are addressed in a716c32.

Red (jq 1.6). The cause was narrower than the object shorthand itself: label is a reserved word in jq 1.6, while {id, title} compiles fine. Every label key and read is now quoted ("label": ."label", $t."label"). I reproduced your numbers with the official jq 1.6 binary: 18/61 before the fix, 77/77 after, and 77/77 under 1.7.1. The live read-only run against corveil-cloud-terraform#618 also passes under 1.6. I did both things you suggested:

  • Fail closed: status now emits {"status":"error"} and exits 2 when its filter can't run. watch requires "status":"ok" before it reads complete, and otherwise exits 2 with WAVE n status unavailable. A test runs this with a jq that can't compile the filter and asserts the wave is never released. Against the old script under jq 1.6, that test fails with the false release you saw.
  • Version gate: preflight rejects jq older than 1.6.
  • CI: the parity job now runs the suite a second time under a checksum-pinned jq 1.6, so 1.7-only syntax can't return unnoticed.

Yellow (contract lines). The stream now binds the ticket before iterating .drift[]. A new test asserts both ⚠ contract: lines for an open PR that has crow:merge and no reviewer request.

Green (merged + still-open PR). I went with a drift line and kept the ticket satisfied, rather than holding the wave. Once the ticket's PR has merged, its dependents can build on it. If the stray PR held the wave, it could stall the cascade indefinitely for work that isn't a dependency. It's now reported as PR #12 still open although #11 already merged this ticket, and SKILL.md tells the Manager to ask the operator whether to close it or move its work to its own ticket. If you'd rather it hold the wave, it's a one-line change in classify.

@dgershman dgershman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code & Security Review

The previous round's jq 1.6 false release and the dropped ⚠ contract: lines are fixed. On this machine's jq 1.6 (/usr/local/bin/jq), bash skills/crow-cascade-epic/cascade_test.sh is 77/77, including the fail-closed watch (exit 2, no COMPLETE) and both contract lines. shellcheck is clean, scripts/check-workspace-custom-instructions.sh passes, and swift build completes.

Critical Issues

None.

Architecture / Existing Patterns

  • Existing pathway: /crow-workspace's setup.sh, run one ticket at a time with the list-worktrees check. Wave layering and the merge watch are new and live in cascade.sh. The skill leaves worktree creation, naming, and prompts on setup.sh, and it keeps a wave off /crow-batch-workspace because of the #1301 registration race. That shape fits.

Security Review

Strengths:

  • GitHub reads go through GraphQL variables (-F). Plan fields are not eval'd.
  • An omitted --gate stays human unless a required approving review is visible and the auto-merge watcher is on.
  • Issue comments and gh pr edit stay off the pre-approved allow list.
  • A status filter jq cannot run becomes {"status":"error"}. watch requires "status":"ok" before it can release a wave. That path exits 2 on jq 1.6.

Concerns:

  • The Yellow below: contract drift, including crow:merge before a human approval, is computed from one session's links, so an open PR on the active session can be invisible while the wave is reported complete.

Code Quality

Yellow — PR links come from a different session than the one status reports. collect_ticket takes the first non-explore work session in list-sessions order (skills/crow-cascade-epic/cascade.sh:175). session_for prefers a session that is not completed or archived (skills/crow-cascade-epic/cascade.sh:248-251). Sessions are appended, so the first row is the oldest.

Reproduced with a fake gh/crow: a completed session linked to merged PR #11, and a later active session linked to open PR #12 (crow:merge, no reviewer, and not a closing reference on the issue). status returned state: merged, complete: true, drift: [], and session.id of the active session, with only PR #11 in prs. The leftover-open-PR line and the human-gate crow:merge check both miss a PR registered only on the later session. Collect links from every matching work session, or from the same session session_for returns.

Yellow — re-running the skill does not advance a GitLab ticket. cascade.sh queries only GitHub issue URLs (skills/crow-cascade-epic/cascade.sh:149-169). A GitLab URL stays no_pr. Once that ticket has a work session, to_launch is empty and complete stays false. Reproduced: status for https://gitlab.com/o/r/-/issues/1 with an active session was {state: no_pr, satisfied: false, complete: false, to_launch: []}. skills/crow-cascade-epic/SKILL.md:432 tells the Manager to check glab mr view and re-run the skill to advance. That re-run calls the same status, and the wave does not move. Point this limitation at --wave after the MR is confirmed, and say a GitLab merge does not release the wave.

Green — a merged PR still satisfies the ticket while another open PR is drift, and the wave still releases. That matches the rationale on the last review: dependents can build on the merged PR. The single-issue case is covered by the leftover-open-PR checks in skills/crow-cascade-epic/cascade_test.sh. The Yellow above is the case where that drift line is never produced.

Green — on jq 1.6, jq -e still exits 0 for a blank line (printf '\n' | jq -e '.status == "ok"' → 0). The watch guard holds because collect_status prints an error object when the filter fails; the suite's "watch never releases a wave it could not read" check passes on this jq 1.6. Comparing the printed status token, rather than relying on -e alone, would keep a blank line from looking like success.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Recommendation: Request Changes — driven by 0 Red, 2 Yellow, 2 Green findings.


🐦‍⬛ Reviewed by Crow via Cursor

Addresses the second PR #1313 review.

Yellow: collect_ticket read PR links only from the first matching work
session (the oldest), while status reported the active one. A PR
registered only on a later session was invisible, and so was its drift
(the leftover-open-PR line, crow:merge before approval). Links now come
from every non-explore work session on the ticket.

Yellow: re-running the skill can't advance past a change that lands as a
GitLab MR, because cascade.sh reads only GitHub. SKILL.md no longer says
a re-run advances it. It says a GitLab merge doesn't release the wave:
confirm the MR with glab, check that status shows every other ticket
satisfied, then advance with --wave n+1. --wave skips the whole wave.

Green: jq 1.6's `-e` exits 0 on empty or blank input. That was a real
false release: a status filter that printed a blank line let watch
release the wave, and an empty plan file passed the object check. Every
`jq -e` in cascade.sh is now jq_true, which compares the printed value
to `true`. New tests cover empty and blank plan files, a blank status
line, and the two-session case. The suite is 89/89 under jq 1.6 and
1.7.1; against the previous commit it fails 10 checks under 1.6,
including the false release.

🐦‍⬛ Generated with Claude Code, orchestrated by Crow

Co-Authored-By: Claude <[REDACTED_EMAIL]>
Crow-Session: 3D47A57F-2215-482F-879E-2593E521624F
@dhilgaertner

Copy link
Copy Markdown
Contributor Author

All four findings are addressed in 826a9e6.

  • PR links from a different session (Yellow). collect_ticket now reads add-link --type pr links from every non-explore work session on the ticket, not the first row. Your repro is now a test: a completed session holds merged Shell CRM enrichment abandoned: Crow debugger worker exceeded lease without completing #51, and an active session holds open CHECK #52 with crow:merge, no reviewer, and no closing reference. prs shows both, session is the active one, and both drift lines appear.
  • GitLab doesn't advance (Yellow). Fixed in SKILL.md. The Limitations section now says a GitLab merge doesn't release its wave, and neither the watcher nor a re-run will. To advance: confirm the MR with glab mr view, check in status that every other ticket in the wave is satisfied, then run --wave n+1. It also warns that --wave skips the whole wave. The no_pr row and Phase 6 point there too.
  • jq -e on a blank line (Green). This one was more than a hardening. Against a716c32 under jq 1.6, a status filter that printed a blank line made watch release the wave, and an empty plan file passed the object check. So I replaced every jq -e in cascade.sh with jq_true, which compares the printed value to true, not just the watch guard. Tests now cover an empty and a blank plan file and a blank status line, for both status and watch.
  • Merged + open PR (Green). No change; noted as agreed.

The suite is 89/89 under jq 1.6 and 1.7.1. Against the previous commit, the new checks fail 3 under 1.7.1 and 10 under 1.6.

@dgershman dgershman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code & Security Review

The last round's jq 1.6 false release and the single-session link read are fixed. On this machine's jq 1.6 (/usr/local/bin/jq), bash skills/crow-cascade-epic/cascade_test.sh is 89/89, including the fail-closed watch. shellcheck is clean on cascade.sh, cascade_test.sh, and scripts/check-workspace-custom-instructions.sh (that guard passes). swift build completes.

Critical Issues

None.

Architecture / Existing Patterns

  • Existing pathway: /crow-workspace's setup.sh, one ticket at a time, with the list-worktrees check. Wave layering and the merge watch are new and live in cascade.sh. The skill does not create worktrees itself, and it keeps a wave off /crow-batch-workspace because of the #1301 registration race. That shape fits.

Security Review

Strengths:

  • GitHub reads go through GraphQL variables (-F / -f). Plan fields are not interpolated into the query.
  • A status filter jq cannot run becomes {"status":"error"}. watch requires "status":"ok" before it can release a wave. That path exits 2 on jq 1.6.
  • Issue comments and gh pr edit stay off the pre-approved allow list. cascade.sh only reads GitHub and Crow.
  • An omitted --gate stays human unless a required approving review is visible and the auto-merge watcher is on.

Concerns:

  • Green — who merged is not part of the gate. crow:merge and self-approval drift are computed only on open PRs. A MERGED PR satisfies the ticket, including one the author merged. On the human gate that is the unprotected-branch case the skill is written for. The skill defines the gate as "the PR merged" and keeps no-self-merge in the coder prompt. That enforcement gap is the author's call.

Code Quality

Yellow — a failed crow list-sessions looks like "no sessions", so to_launch starts the ticket again.

collect_status stores any list-sessions failure as an empty list and still returns "status":"ok" (skills/crow-cascade-epic/cascade.sh:321-322):

sessions=$("$CROW_BIN" list-sessions 2>/dev/null | jq -c '.sessions // []' 2>/dev/null) || sessions='[]'
[[ -n "$sessions" ]] || sessions='[]'

to_launch then treats session == null plus no_pr or pr_closed as launchable (skills/crow-cascade-epic/cascade.sh:310-313). The ticket error stays null: this path never records that Crow could not be read. skills/crow-cascade-epic/SKILL.md:412 says a re-run skips any ticket that already has a work session.

Reproduced with CROW_BIN exiting 1 and an open issue that has no PRs: status returned {"status":"ok","to_launch":["o/r#10"]} and error: null. Phase 5 launches to_launch. A socket timeout while the coder is still working and has not opened a PR yet starts a second session. The jq-filter failure beside this is fail-closed; this one is not. A non-zero list-sessions should be a status error (watch exit 2), not an empty session list. Cover that in cascade_test.sh.

Green — pr_closed is relaunched only when no session row remains. A completed session plus a closed-unmerged PR stays out of to_launch, so the wave waits until that session is gone. The idempotency section already says any session blocks a second launch. The state table's "launchable again" is the case with no session left; one clarifying phrase would stop a re-run from looking stuck.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Recommendation: Request Changes — driven by 0 Red, 1 Yellow, 2 Green findings.


🐦‍⬛ Reviewed by Crow via Cursor

…2 review)

Addresses the third PR #1313 review.

Yellow: a failed `crow list-sessions` was stored as an empty session
list, so status came back "ok" with every in-flight ticket in to_launch.
A socket timeout could have started a second coder on a ticket still
being worked. Now a non-zero exit, or output that isn't a session list,
is a status error: status exits 2 with no to_launch, and watch exits 2
without releasing anything.

Green: who merged is now partly part of the gate. On the human gate, a
merged PR with no approval from anyone but its author is reported as
drift. It is checked by approval, not by mergedBy: on #618, GitHub
records the author as the merger of every PR (#636, #640, #644),
because Crow's auto-merge ran under that account after the reviewer
approved. The drift doesn't hold the wave, since the change has already
landed. SKILL.md tells the Manager to raise it with the operator
(revert?). The live #618 status still reports no drift.

Green: the pr_closed row now says the ticket is relaunched only once no
work session remains, and how to restart it.

Suite: 102/102 under jq 1.6 and 1.7.1. Against the previous commit, 10
checks fail, including the reviewer's repro (a ticket offered for launch
while Crow was unreachable).

🐦‍⬛ Generated with Claude Code, orchestrated by Crow

Co-Authored-By: Claude <[REDACTED_EMAIL]>
Crow-Session: 3D47A57F-2215-482F-879E-2593E521624F
@dhilgaertner

Copy link
Copy Markdown
Contributor Author

All three findings are addressed in 294bc27.

The suite is 102/102 under jq 1.6 and 1.7.1.

@dgershman dgershman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code & Security Review

The last round's fail-closed list-sessions path holds. On this machine's jq 1.6 (/usr/local/bin/jq), bash skills/crow-cascade-epic/cascade_test.sh is 102/102. swift build completed. CI is green (Build & Test, Catalog & Parity Gates, Shell Lint).

Critical Issues

None.

Architecture / Existing Patterns

  • Existing pathway: /crow-workspace's setup.sh, one ticket at a time, with the list-worktrees check. Wave layering and the merge watch live in cascade.sh. That shape fits.

  • Yellow — human-gate approval is read from latestReviews, which drops a review that still stands. Crow already treats approval as reviewDecision first and only then falls back to latestReviews, because a re-request removes the review from latestReviews while the verdict remains (Packages/CrowEngine/Sources/CrowEngine/PRStatusController.swift:481, Packages/CrowCore/Sources/CrowCore/Models/PRStatus.swift:165). skills/crow-cascade-epic/cascade.sh:150 selects latestReviews, and cascade.sh:225 keeps a login only when that node is APPROVED. The fragment does not select reviewDecision or latestOpinionatedReviews.

    Reproduced with a fixture in the shape of merged corveil/crow#1261: reviewDecision is APPROVED, latestOpinionatedReviews is dhilgaertner APPROVED, latestReviews is empty, and a review request for dhilgaertner is still pending. status returned state: merged, satisfied: true, and drift PR #1261 merged without an approval from anyone but its author (human gate). skills/crow-cascade-epic/SKILL.md:385 tells the Manager to report that unreviewed code is on the default branch. Count approvals from latestOpinionatedReviews (still exclude the author). The Resources template is the same script.

Security Review

Strengths:

  • GitHub reads go through GraphQL variables. Plan fields are not eval'd.
  • A missing or unreadable crow list-sessions is {"status":"error"}. watch requires "status":"ok" before it can release a wave.
  • Issue comments and gh pr edit stay off the pre-approved allow list.
  • An omitted --gate stays human unless a required approving review is visible and the auto-merge watcher is on.

Concerns:

  • The Yellow above pages the operator that a merged PR had no human approval when the approval is only missing from latestReviews.

Code Quality

Yellow — the "who added crow:merge" command does not run as written. skills/crow-cascade-epic/SKILL.md:380 (and Resources/crow-cascade-epic-SKILL.md.template) puts the jq filter in a table cell, so the pipes are backslash-escaped: [.[] \| select(...)] \| last \| .actor.login. Reproduced: jq rejects \| with unexpected INVALID_CHARACTER and exits 3. That command is how the Manager decides whether a premature crow:merge was the coder's (remove it) or the reviewer's (leave it). Put the filter in a fenced block so the pipes are real.

Green — a merged PR with no non-author approval still satisfies the ticket and releases the wave. The skill states that on purpose: the change has landed, and holding dependents would not undo it. Who clicked merge stays out of the gate.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Recommendation: Request Changes — driven by 0 Red, 2 Yellow, 1 Green findings.


🐦‍⬛ Reviewed by Crow via Cursor

…OW-1312 review)

Addresses the fourth PR #1313 review.

Yellow: approvals came only from latestReviews, which GitHub hides once
the reviewer is re-requested, even though the approval still stands (the
case PRStatus.swift already handles). A merged PR like #1261
(latestReviews empty, latestOpinionatedReviews APPROVED, a re-request
pending) was reported as "merged without an approval from anyone but its
author", which would have told the operator unreviewed code had landed.
The fragment now also selects latestOpinionatedReviews, and approved_by
and reviewed_by read both lists. The author is still excluded. The same
fix clears a false crow:merge-before-approval warning on open PRs.

Yellow: the "who added crow:merge" command sat in a SKILL.md table cell,
so its pipes were `\|` and jq rejected it (the Manager reads the file
raw). It is now in a fenced block and runs as written. The guard script
now rejects any `\|` in either copy of the skill.

Suite: 104/104 under jq 1.6 and 1.7.1. The two #1261-shaped checks fail
against the previous commit. The live #618 status is unchanged: ok,
complete, no drift.

🐦‍⬛ Generated with Claude Code, orchestrated by Crow

Co-Authored-By: Claude <[REDACTED_EMAIL]>
Crow-Session: 3D47A57F-2215-482F-879E-2593E521624F
@dhilgaertner

Copy link
Copy Markdown
Contributor Author

Both Yellows are addressed in 649f842.

  • Approvals hidden by a re-request. The fragment now also selects latestOpinionatedReviews, and approved_by / reviewed_by read both lists, still excluding the author. I checked Let Scratch Ticket pick a repo from a dropdown #1261 live: latestReviews: [], latestOpinionatedReviews: dhilgaertner APPROVED, a pending re-request, reviewDecision: APPROVED, which is exactly your shape. That fixture is now a test and shows no drift. The same change clears a false crow:merge-before-approval warning on an open PR whose approval was re-requested. Both checks fail against 294bc27.
  • The crow:merge lookup didn't run. It's now in a fenced block under the drift table and runs as written; pointed at this PR it returns dhilgaertner. Since the Manager reads SKILL.md raw, the guard script now fails on any \| in either copy of the skill. That also changed the only other occurrence, --gate human\|auto, which was docs, not a command.
  • Green: acknowledged; no change.

The suite is 104/104 under jq 1.6 and 1.7.1. The live #618 status is unchanged: ok, complete, no drift.

@dgershman dgershman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code & Security Review

The last round's two findings are fixed. On this machine's jq 1.6, bash skills/crow-cascade-epic/cascade_test.sh is 104/104. shellcheck is clean on cascade.sh, cascade_test.sh, and scripts/check-workspace-custom-instructions.sh (that guard passes). swift build completed.

Critical Issues

None.

Architecture / Existing Patterns

  • Existing pathway: /crow-workspace's setup.sh, one ticket at a time. This commit extends the status query's review fragment. Crow's own PR status reads reviewDecision first (Packages/CrowEngine/Sources/CrowEngine/PRStatusController.swift:481). The cascade also needs the approver's login, so skills/crow-cascade-epic/cascade.sh:227 unions latestReviews with latestOpinionatedReviews — the list GitHub keeps after a re-request. Live corveil/crow#1261 is reviewDecision: APPROVED, latestReviews empty, and latestOpinionatedReviews holding the approval. skills/crow-cascade-epic/cascade_test.sh covers that shape, including a crow:merge PR whose only approval sits in latestOpinionatedReviews.

Security Review

Strengths:

  • GitHub reads go through GraphQL variables. Plan fields are not eval'd.
  • A missing crow list-sessions, or a status filter jq cannot run, is {"status":"error"}. watch requires "status":"ok" before it can release a wave.
  • Issue comments and gh pr edit stay off the pre-approved allow list.
  • An omitted --gate stays human unless a required approving review is visible and the auto-merge watcher is on.

Concerns:

None.

Code Quality

The "who added crow:merge" lookup in skills/crow-cascade-epic/SKILL.md:390 is a fenced command, so the pipes are real. The same filter returns the actor under jq 1.6. scripts/check-workspace-custom-instructions.sh rejects a table-escaped pipe in either copy of the skill.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Recommendation: Approve — driven by 0 Red, 0 Yellow, 0 Green findings.


🐦‍⬛ Reviewed by Crow via Cursor

@dhilgaertner
dhilgaertner merged commit 058eb27 into main Oct 1, 2026
5 checks passed
@dhilgaertner
dhilgaertner deleted the feature/crow-1312-cascade-epic-skill branch October 1, 2026 00:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crow:merge Crow auto-merge on green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Skill: crow-cascade-epic — orchestrate a dependency-gated ticket cascade from an epic

2 participants