Notes on wiring GitHub Projects and Orca into a working AI coding pipeline, pitfalls included. macOS as the example.

Background: I have a side project (panta-log, a local-first voice review tool) whose issue/PR conventions had been sitting in another repo, unused. This time the goal was concrete: a kanban board for tasks, Orca running the agents, and the whole chain of “pick a task → develop → test → merge → status transitions itself” working end to end within one day.

Here’s what the finished loop looks like:

Pick a card (Todo) → open a worktree from it in Orca → agent works (runs tests itself)
→ PR triggers CI → review the diff → squash merge
→ issue closes itself → card moves to Done by itself → remote branch deleted by itself

Step 1: getting the conventions in place

I copied a set straight from my own boss-zhipin-scraper project: three issue templates (bug/feature/question as YAML forms), a PR template, and a CONTRIBUTING.md, adapted to the new project’s stack (uv, pytest, the macOS permission gotchas). Push it and the repo’s New issue page immediately serves proper forms. Five minutes, nothing more to say.

Step 2: backlogging with the gh CLI

The roadmap doc already had six work packages with acceptance criteria, so I just split them into issues. Ground rules:

  • Only turn tasks marked “needs rework” or “needs building” into issues. Skip what already works — don’t pollute the board
  • Prefix titles with the task code (WP-A1 and friends) so cards map back to the doc at a glance
  • Quote the roadmap’s acceptance criteria verbatim in the body, plus a suggested week batch
  • Hang everything on a milestone whose description states the exit criteria

18 issues. Two for loops.

Step 3: the pitfall zone — gh project CLI

Most board operations can be done from the terminal, but the CLI trips you up at every turn. All of these bit me for real:

Pitfall 1: gh auth refresh needs -h in non-interactive mode

Running it through a non-interactive prefix (like Claude Code’s !) fails with:

--hostname required when not running interactively

Write it as gh auth refresh -h github.com -s read:project,project. Also note this command blocks on device authorization: run it in the background, fish the one-time code out of the output, enter it at github.com/login/device.

Pitfall 2: field-create takes --name, not --title

gh project field-create --title "Phase" dies with “unknown flag”. The help text has shown both spellings in different eras; --name is the one that works.

Pitfall 3: -q requires --format json

The sneakiest one. gh project item-add <n> --owner me --url xxx -q '.id' fails quietly with cannot use --jq without specifying --format json. In a batch script all 18 items die invisibly (stderr swallowed). Correct form:

gh project item-add 2 --owner eatmoreduck --url "https://github.com/xxx/issues/1" --format json -q '.id'

Pitfall 4: item-edit uses --project-id, not --owner + --project-number

item-add takes number + owner, then item-edit suddenly wants the project’s global ID (a PVT_xxxx string, visible in the project list):

gh project item-edit --id <item-id> --project-id PVT_xxxx \
  --field-id <field-id> --single-select-option-id <option-id>

Pitfall 5: item-list’s JSON returns fieldValues as null

Want to verify your field values landed? gh project item-list --format json returns null for everything — the endpoint simply doesn’t return them. GraphQL is the only way to check:

gh api graphql -f query='query($login:String!,$number:Int!){
  user(login:$login){ projectV2(number:$number){
    items(first:30){ nodes{
      content{ ... on Issue { number } }
      fieldValues(first:20){ nodes{
        ... on ProjectV2ItemFieldSingleSelectValue { name field{ ... on ProjectV2FieldCommon { name } } }
      } }
    } } } }
}' -f login=eatmoreduck -F number=2 --jq '...'

Two single-select fields (Phase for release stages, Area for modules), 18 cards batch-added with Status/Phase/Area set, plus two date fields for the timeline. One pass.

Orca: easier than expected

I expected a GitHub OAuth dance inside Orca. Instead, the integrations page showed GitHub as Connected out of the box — Orca’s GitHub integration reads the local gh CLI credentials, so a logged-in gh means already connected. There is no “connect and authorize” button to find.

Orca integrations page showing GitHub already Connected

One small confusion: where’s the board? The Tasks page has three tabs — Issues, PRs, Projects. The issue list lives under Issues; the board lives under the Projects tab. Right-click any card to create a worktree, and the composer pre-fills the task name and links the issue.

The three tabs of Orca’s Tasks page

Board automation: on by default

The engine of the whole loop is two built-in project workflows:

  • Item added to project → Set Status = Todo
  • When pull request merged → Set Status = Done

Counter-intuitive bit: both ship ON the moment the project is created. I assumed they needed flipping in the web UI and put that on my checklist; when I finally opened the Workflows panel, they’d been working all along. Double-checked out of curiosity: gh CLI has no subcommands for workflows at all (gh project --help only covers items and fields), so you can’t toggle them from the terminal either way — the project’s Workflows panel is the only place to view or adjust them.

The Pull request merged workflow configuration

Once these are live, the moment a PR merges: the issue closes itself (via Closes #n in the commit), the card moves to Done. No human involved.

Roadmap timeline: dates can be piped in from the CLI

The timeline view opens blank because no item has dates. The fix:

  1. Create Start date / Target date date fields
  2. Point the view toolbar’s Date fields at those two
  3. Pipe the values in per roadmap batch:
gh project item-edit --id <item-id> --project-id PVT_xxxx \
  --field-id <start-field-id> --date "2026-09-14"

Then the timeline shows real bars, and dragging them reschedules the underlying fields.

The roadmap timeline view

Multiple agents: yes, but don’t get greedy

Orca’s core trick is one task per isolated git worktree, and several agents genuinely do run in parallel without stepping on each other’s branches. Verified.

Hard-won practical notes:

  • Avoid file conflicts when picking concurrent cards. Two tasks touching the same file means the later merge conflicts for sure. Same-subsystem tasks shouldn’t run in parallel
  • 2-3 cards in parallel, merges serial. The bottleneck is never writing code — it’s review. Five agents means five PRs you can’t properly review, and blind-merging into your core pipeline is free soloing
  • The agents dashboard’s three columns (Needs you / Running / Done) are exactly the management surface for this: an agent that stops to ask shows up under “Needs you”

The agents dashboard

Also: Orca does not pull cards off the board by itself. Which cards run and when is still a human decision — and honestly, that’s a feature.

Hand the CI to an agent too

The repo had no CI, so PR-level automated testing was a gap. My approach: file an issue with the constraints spelled out first — private repo means billable Actions minutes and macOS runners bill at 10x, so the workflow must stay on ubuntu; one workflow file only; no ESLint/mypy. Then open a worktree and hand it to an agent.

It checked every acceptance box: backend (ruff + 88 tests) and frontend build, both jobs green, about 40 seconds combined. It even hit a real snag and fixed it itself — ubuntu’s sounddevice lacks PortAudio, so it added apt-get install libportaudio2 to the CI. Best of all, it let the first run go red on purpose before fixing it, proving the failure alarm actually fires.

PR checks all passing

Lesson worth keeping: the more specific the issue (constraints, boundaries, acceptance criteria), the less babysitting the output needs. “Add me a CI” and a fully specified issue produce wildly different results.

Review and merge without leaving Orca

Diff review, feedback, merging — all in-app:

  • Line-by-line review in the diff viewer; if something’s off, say so in that worktree’s agent session, and the push updates the same PR
  • The Checks panel shows CI status, and a failed check can be handed straight to the agent to fix

Then comes the most instructive misuse of the day: I couldn’t find the merge button, and the “commit” button in the corner was grayed out.

The gray is correct — that’s the git push button, and there was nothing left to push. The merge control lives in the PR detail view (the page you get by clicking into the PR from the list), with a merge-method dropdown, same logic as the web.

The right-side menu is git operations, not the merge entry

Merge method: squash, no hesitation

One line each:

  • Squash: the whole PR becomes a single commit on master; intermediate commits are discarded
  • Rebase: every commit is replayed individually; all intermediates survive
  • Merge commit: merges as-is plus a merge node; history becomes a graph

For AI agent output, squash is the only right answer. An agent’s intermediate commits are all “fix typo” and “round 2” noise with zero archival value. After squashing, one card maps to one clean commit on master, a revert is one command, and Closes #n still works from the PR description. The other two only matter when you need branch topology for team collaboration, or you’ve hand-curated a commit series worth preserving.

Repo settings: two toggles, two different stories

  • Automatically delete head branches: auto-deletes the remote branch after merge. Instant benefit — otherwise twenty cards leave twenty dead branches behind. Local worktrees are unaffected; clean those up in Orca when done
  • Allow auto-merge: lets a PR be set to “merge when green”. Note this only unlocks the feature — nothing happens until you enable it on a specific PR. And with no CI, it’s a no-op — there are no checks to wait for. So the order is: turn on branch deletion now, auto-merge after CI lands

End of day one

ItemStatus
Issue/PR templates + contributing guidePushed and live
19 issues + milestone + board fieldsAll on the board, verified via GraphQL
Two board automationsOn by default, zero config
Roadmap timelineThree batches of dates piped in
CI (agent-built)Merged and live, both jobs green
The full loopFirst card verified from pick-up to Done

The biggest takeaway: once this scaffolding exists, the act of “managing tasks” disappears — what’s left is moving cards one by one. Every tool handles its stretch of the pipe: GitHub Projects keeps the state, Orca handles agents and worktrees, and the gh CLI patches every gap in between.

The plan from here is to build the habit: open the board first thing each day, pick two cards. The board doesn’t lie, so progress doesn’t need remembering.