Team recap · Diagnostic session · 2026-07-23

Why ship-autonomous falls back to CI polling locally

Someone ran our automated "ship" workflow on their own laptop and saw a message: falling back to synchronous CI polling. Nothing broke — that fallback is by design. This recap explains why it happens, and answers the follow-up: no, polling does not cost the tokens you'd expect.

2
Findings documented
0
Code changes
1
File read
1
Open item

This was a diagnostic session, not a coding one — no files in the repository changed. The deliverable is the understanding itself: two things the whole team can now rely on instead of re-deriving. First, the live-update path the workflow prefers only exists in cloud environments, so a laptop correctly uses the polling fallback. Second, that fallback's "waiting" is free in token terms — the cost, when there is one, comes from output volume, not from time spent waiting.

What changed

Nothing in the repo — two things we now understand

Both items below document how existing behavior works. Read the color code once and it holds for the rest of the page: local · polling and cloud · subscription.

Finding 1

Local runs poll CI; only cloud runs get live updates

The workflow's preferred path is to subscribe to pull-request events and react as they arrive. That needs a tool that exists only in remote environments — Claude Code on the web, or a GitHub Action. On a laptop the tool is simply absent, so the workflow does the correct thing: it polls the checks until they finish. The message you saw is that switch happening, not a failure.

Who it affects Anyone running the ship workflow locally
How to reach it /git-agent:ship-autonomous
Want live updates? Run it from Claude Code on the web or a GitHub Action
Finding 2

Polling does not burn tokens the way it sounds like it would

"Polling" sounds like the model checking over and over. It isn't. The workflow waits inside a single blocking command, and the model does no thinking — and spends no tokens — while that command waits for CI. Cost appears only from the text the command finally prints, and from any fix-and-recheck loops. Waiting ten minutes costs the same as waiting thirty seconds.

Who it affects Anyone weighing the cost of shipping locally
When it applies Every local CI watch — no action needed

How it works now

The two paths, and why waiting is free

Yes: cloud (web / GitHub Action)

No: local terminal

Step 5: PR is open, CI is running

Is the subscribe toolavailable here?

Subscribe to PR events

End the turn — nothing waits

A CI event wakes the sessionwhen something changes

Fall back to polling

One blocking commandwaits until CI settles

All results return at once

Fix failures, then merge when green

Follow the branch. The left path exists only in the cloud; a laptop always takes the right path. Same destination, different way of waiting.
Shell / CIModel (billed per turn)Shell / CIModel (billed per turn)Polling mode — localSubscription mode — cloudeach event is a fresh turnthat re-reads the whole conversationstart one blocking watch commandCI runs for minutes — model idle, zero tokensreturn every result in one outputsubscribe, then end the turnevent — a check finishedevent — a review comment
Watch what costs tokens. Polling holds one command open (the idle stretch is free); subscription wakes once per event, and every wake re-reads the conversation. Time is not the cost — turns are.

Assumption vs. reality

What people expect, and what actually happens

Nothing changed in code, so the useful "before/after" here is the gap between the common assumption and how the workflow really behaves.

What you might assumeWhat actually happens
The fallback message means something failedExpected. The cloud-only live-update tool is absent on a laptop, so the safe fallback runs.
Polling means checking over and over, burning tokensThe wait is one blocking command. The model is idle and bills nothing while CI runs.
A 10-minute CI wait costs more than a 30-second oneSame cost. Waiting is free; only the returned output is billed, once.
Subscription mode is always cheaperOnly for chatty PRs. For a quiet PR that goes green once, polling is cheaper — one turn, then done.
It keeps handling review comments after merge time, locallyNo. Locally it stops after merging. The keep-watching behavior needs subscription mode.

Decisions

What we concluded, and what we ruled out

Treat the fallback as correct, not a bug

Polling is a complete path to the same outcome — watch CI, fix failures, merge when green. The only local losses are cosmetic (the turn stays busy) and post-merge (no ongoing comment handling).

Rejected · warn or block harder locally
Adds friction to a workflow that already does the right thing.

Rejected · require running remotely
Removes a real capability (local shipping) to solve a non-problem.

Leave the watch command as-is

The single blocking watch command is the simplest thing that waits until CI settles, and its cost is fine for normal runs.

Rejected · swap in a sleep-then-check loop
It would shrink the printed output but add model turns, and today's output isn't big enough to justify the change. Kept as an option if watch output ever bloats a session — see Open items.

Learnings

Gotchas worth keeping

  • "Available on lookup" is not "present here." The workflow looks the subscribe tool up at runtime; the lookup returns nothing locally because the environment doesn't host it. Loadable-on-demand and actually-available are different things.
  • Blocking waits are free in token terms. Cost tracks model turns, not wall-clock time. One command that blocks for ten minutes is still one turn.
  • The real token driver is re-reading context. Every turn re-sends the whole conversation as input, so many cheap wake-ups (subscription on a chatty PR) can total more than one big wait (polling).
  • Only ship-autonomous hits this. The commit and PR skills finish immediately, so they never reach for the subscribe tool. The split only shows where the workflow watches CI over time.

Open items

One optional, unadopted idea

Deferred · not adopted

Trim watch output with a sleep-then-check loop

If the watch command's repeated status tables ever noticeably bloat a session's context, replace the single blocking --watch with a short loop that sleeps and then asks for the compact JSON status. Trade-off: a couple more model turns for a much smaller output footprint. Only worth doing if the bloat is actually observed.

Where ship-autonomous/SKILL.md Step 5 (≈ lines 195–211)

Files touched

Read only, plus what this session produced

Read (no changes)

  • kit/plugins/git-agent/skills/ship-autonomous/SKILL.md — traced the Step 5 subscribe-or-poll branch and the fix-and-recheck loop.

Produced by this session

  • docs/plans/sessions/document-ship-autonomous-ci-polling-session.md — this committed recap record.

Glossary

Terms, one line each

ship-autonomous
The automated "ship" workflow: commits, opens a pull request, watches CI, fixes some failures, and merges when everything is green.
CI (Continuous Integration)
The automated checks — tests, linting, type checks — that run against a pull request.
PR (Pull Request)
A proposed set of changes submitted for review and merge.
Subscription mode
The preferred path: subscribe to PR events and let them wake the session. Cloud-only.
Polling mode
The fallback path: run one blocking command that waits until CI finishes, then read the results. Used locally.
Deferred tool
A tool whose full definition loads on demand rather than at startup — loadable only if the current environment actually hosts it.
ToolSearch
How the workflow looks a deferred tool up at runtime; it returns nothing when the tool isn't present.
MCP (Model Context Protocol)
The plug-in system that supplies extra tools — like GitHub event subscription — to a session.
Inference turn
One round of the model thinking and responding; the unit that actually costs tokens.
Context re-billing
Every turn re-sends the whole conversation as input, so more turns means more repeated cost.
gh pr checks --watch
The GitHub CLI command that blocks and prints CI status until every check finishes.

Session d76058d4 · recap generated 2026-07-23 · source: ship-autonomous/SKILL.md