Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins/compound-engineering/skills/ce-dispatch/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Config keys and resolution:
|---|---|---|
| `dispatch_mode` | `conductor`, or another short identifier | `conductor` |
| `dispatch_branch_prefix` | any string (no leading/trailing slashes) | `dispatch/` |
| `dispatch_base_branch` | any branch name | repo's default branch (`git symbolic-ref refs/remotes/origin/HEAD`) |
| `dispatch_base_branch` | any branch name | repo's default branch (`git symbolic-ref --short refs/remotes/origin/HEAD`) |
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🟡 --short alone returns origin/main, not bare branch name main

git symbolic-ref --short refs/remotes/origin/HEAD returns origin/main, not the bare branch name main. I confirmed this by running the command in this repo (returns origin/main). Every other skill in this repo that needs a bare branch name adds an extra stripping step: ce-code-review/scripts/resolve-base.sh:32 uses --short | sed 's#^origin/##', and ce-work/SKILL.md:68 / ce-work-beta/SKILL.md:121 use | sed 's@^refs/remotes/origin/@@'. The ce-dispatch SKILL.md uses only --short without stripping, so the resolved dispatch_base_branch default will be origin/main. This value propagates into dispatch metadata (base_branch: at SKILL.md line 120) and the dispatched agent's PR-target instructions — where origin/main is not a valid GitHub branch name for gh pr create --base.

The test comment at lines 347-350 also incorrectly states --short returns "the bare branch name (main)".

Prompt for agents
The git symbolic-ref --short flag abbreviates refs/remotes/origin/main to origin/main, not the bare branch name main. Every other skill in this repo strips the origin/ prefix after symbolic-ref:

- ce-code-review/scripts/resolve-base.sh:32 uses: git symbolic-ref --quiet --short refs/remotes/origin/HEAD | sed 's#^origin/##'
- ce-work/SKILL.md:68 uses: git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'
- ce-work-beta/SKILL.md:121 uses the same sed pattern as ce-work

Since SKILL.md is an LLM instruction (not a shell script), the fix should either:
1. Show the full pipeline including sed to strip origin/ (matching the ce-work pattern), or
2. Add prose clarifying the agent should extract only the branch name portion (e.g., 'strip the origin/ prefix from the output')

Also update the test comment at tests/skills/ce-dispatch-contract.test.ts:347-350 which incorrectly claims --short returns 'the bare branch name (main)' — it returns origin/main.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

| `dispatch_labels` | comma-separated label list | `ce-dispatch` |
| `dispatch_auto_review` | `true` or `false` | `true` |

Expand Down Expand Up @@ -164,7 +164,7 @@ Dispatch status: <count merged> / <total units> merged. <count open> open PRs. <

Act on the user's selection — do not just announce it. The bare per-option action lives inline below. Elaborate sub-flows (review tool selection, conflict resolution prose) live further down.

- **Check PR status (1)** — for each dispatched unit, run `gh pr list --search "head:<expected_branch>"` (or query by linked issue if the workspace renamed the branch); for each match, run `gh pr view <number> --json state,mergeable,statusCheckRollup,headRefName`. Update `dispatched_units` with the latest PR number, state (`OPEN`, `MERGED`, `CLOSED`), CI rollup, and mergeable flag. Re-render the loop status line and re-render the menu.
- **Check PR status (1)** — for each dispatched unit, run `gh pr list --state all --search "head:<expected_branch>"` (or query by linked issue if the workspace renamed the branch); `--state all` is required because `gh pr list` defaults to open PRs only and would otherwise miss PRs merged outside this orchestrator (GitHub UI, Conductor, another shell). For each match, run `gh pr view <number> --json state,mergeable,statusCheckRollup,headRefName`. Update `dispatched_units` with the latest PR number, state (`OPEN`, `MERGED`, `CLOSED`), CI rollup, and mergeable flag. Re-render the loop status line and re-render the menu.

- **Review a PR (2)** — ask the user which U-ID's PR to review (blocking tool single-select from open PRs in `dispatched_units`). Then invoke the `ce-code-review` skill via the platform's skill-invocation primitive (`Skill` in Claude Code, `Skill` in Codex, the equivalent on Gemini/Pi), passing the PR URL as the argument. When `dispatch_auto_review: true`, also auto-trigger this for every newly opened PR before the user is asked to merge it (record per-PR `reviewed: true` so it isn't re-run).

Expand Down
51 changes: 51 additions & 0 deletions tests/skills/ce-dispatch-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,57 @@ describe("ce-plan post-generation menu surfaces dispatch as a fifth option", ()
})
})

describe("ce-dispatch SKILL.md regression guards (Codex-flagged bugs)", () => {
// Both guards target real bugs flagged by the upstream's chatgpt-codex-connector
// bot on EveryInc#762. Without these, the original `gh pr list` and
// `git symbolic-ref` invocations silently return the wrong data.

test("Phase 4 status refresh queries merged PRs, not just open ones", () => {
// `gh pr list` defaults to open PRs only (CLI manual: "only lists open PRs"
// by default). Dispatched PRs merged outside this orchestrator (GitHub UI,
// Conductor, another shell) must still be discovered, otherwise the
// dependency graph never advances and `Dispatch newly unblocked units`
// can stay stuck even after prerequisites are merged. Required: --state all
// (or --state merged on a separate pass).
const phase4Start = SKILL_BODY.indexOf("### Phase 4:")
expect(phase4Start).toBeGreaterThan(-1)
const phase4Region = SKILL_BODY.slice(phase4Start)
// Match `gh pr list` invocations (those that include flags/arguments,
// identified by the `--search` flag we always pass) and require a state
// flag on each. A bare prose mention of `gh pr list` without arguments
// is not an invocation and is exempt. Allow `--state all` or
// `--state merged`.
const ghPrListInvocations =
phase4Region.match(/gh pr list[^\n`]*--search[^\n`]*/g) ?? []
expect(ghPrListInvocations.length).toBeGreaterThan(0)
for (const inv of ghPrListInvocations) {
expect(inv).toMatch(/--state (all|merged)/)
}
})

test("dispatch_base_branch default uses --short to return a bare branch name", () => {
// `git symbolic-ref refs/remotes/origin/HEAD` without --short returns the
// full ref path (refs/remotes/origin/main) rather than the bare branch
// name (main). That value gets propagated into dispatch metadata / agent
// prompt instructions where a plain branch name is expected, breaking
// PR-target instructions in dispatched workspaces.
const phase0Start = SKILL_BODY.indexOf("### Phase 0:")
const phase1Start = SKILL_BODY.indexOf("### Phase 1:")
expect(phase0Start).toBeGreaterThan(-1)
expect(phase1Start).toBeGreaterThan(phase0Start)
const phase0Region = SKILL_BODY.slice(phase0Start, phase1Start)
// Every `git symbolic-ref ... refs/remotes/origin/HEAD` invocation in
// Phase 0 must include the --short flag.
const symbolicRefMatches =
phase0Region.match(/git symbolic-ref[^`\n]*refs\/remotes\/origin\/HEAD/g) ??
[]
expect(symbolicRefMatches.length).toBeGreaterThan(0)
for (const inv of symbolicRefMatches) {
expect(inv).toContain("--short")
}
})
})

describe("conductor-notes.md documents key Conductor behavior", () => {
const requiredHeadings = [
"Issue-to-workspace lifecycle",
Expand Down