Skip to content

Add a steward skill that keeps ready agent PRs mergeable #101

Description

@V3RON

Why

work-issue ends when a PR is marked ready for review. From then on nobody owns it. When another PR merges first the branch goes behind or conflicting with main; when a human requests changes, no agent sees the comments until someone opens a session and asks. The PR for #98 sat conflicting with main after #99 merged, with its ledger saying "Ready: no", until a human noticed. Every agent PR that waits more than a day pays this.

Claude Code on the web reacts to review comments only on PRs it opened itself, and its docs say it cannot react to conflicts at all because GitHub sends no webhook when the base advances. PRs driven from the desktop app or OpenCode get nothing.

Expected outcome

A steward-pr skill that, invoked on a schedule or by hand, keeps every ready agent PR mergeable and answered until a human merges it. One sweep over a repo with nothing to do posts nothing and changes nothing.

For each open, non-draft PR whose body carries the work-issue ### Status section, the steward:

  • brings the branch up to date with main (update-branch when behind, merge origin/main into the branch when conflicting) and gets checks green again, delegating fixes to implement-issue;
  • re-runs e2e-device when the update touched packages/native, packages/react-native, playground or playground-native;
  • treats every PR comment, review and review comment without the agent trailer as human input, delegates the fix to implement-issue with the comments pasted in, runs one review-pr round on the fix, pushes, and replies on each thread saying what changed and in which commit;
  • updates the ledger with a Steward: entry and ends with a fixed report block.

So that the steward can tell agent activity from human activity regardless of which GitHub login posted it, every comment, review, PR body and issue comment an agent posts ends with one trailer line:

-- agent: <skill-name>

Constraints and non-goals

  • Never gh pr merge (AGENTS.md rule 8), never rebase, never force push. Update by merging main in; squash merge makes the merge commit disappear.
  • Never resolves review threads. The human who opened a thread resolves it.
  • Drafts stay with their orchestrator. A draft with a stale ledger is an unfinished work-issue run, resumed by running work-issue on its issue again.
  • PRs without the ledger (human-authored, dependabot) are never touched.
  • Same loop discipline as work-issue: at most two fix rounds per PR per sweep, then one comment on the linked issue with what is stuck, status:blocked, stop.
  • Reads review activity through the REST API (gh api repos/.../pulls/N/comments, .../reviews, .../issues/N/comments), not GraphQL. Claude Code cloud sessions proxy GitHub and allow only a pinned set of GraphQL operations; the skill must work there too.
  • Scheduling is the operator's choice (/loop, a scheduled task, a cron). The skill assumes nothing about how often it runs.
  • Stacked PRs are out of scope; the repo keeps sequential slices against main.
  • No CI changes.

How we know it is done

  1. AGENTS.md names the trailer under Writing, and every skill that posts to GitHub (review-pr, work-issue, implement-issue, design-feature, triage-issue, file-issue, cut-release, review-memory) appends it in its posting step. A dry run of review-pr on a branch produces a review whose summary and every inline comment end with -- agent: review-pr.
  2. .claude/skills/steward-pr/SKILL.md exists and the Skills table in AGENTS.md routes "watching PRs, keeping them mergeable, answering review" to it.
  3. Invoked with no argument, the steward sweeps gh pr list --state open and selects only non-draft PRs whose body contains ### Status. Invoked with a PR number, it handles that PR alone and reports "skipped: " when it does not qualify.
  4. On a PR whose mergeStateStatus is BEHIND, one sweep runs gh pr update-branch; on DIRTY, it merges origin/main into the branch, resolves, pushes, and the PR reads CLEAN or UNSTABLE afterwards. No command in the skill rewrites history.
  5. On a PR with failing checks, one sweep delegates to implement-issue with the failing job's log excerpt and ends with checks green or the PR blocked after two rounds.
  6. On a PR where a human left a review comment (no trailer), one sweep ends with a commit addressing it, one review-pr round on that commit, and a reply on that thread carrying -- agent: steward-pr. The thread stays unresolved. A second sweep over the same PR posts nothing.
  7. After each handled PR the ### Status line gains Steward: <date> <what was done> and the skill ends with a fixed report block listing, per PR, what was done or why it was skipped.
  8. .claude/settings.json allowlists the commands the skill runs and nothing broader (gh pr update-branch *, the REST reads under gh api repos/*).
  9. A sweep over a repo where every qualifying PR is clean, green and answered posts nothing and pushes nothing.

Alternatives considered

  • Fold this into work-issue as a final phase: rejected, its trigger is many PRs on a timer, not one issue in one session, and the two would blur the handoff at "ready".
  • Rely on Claude Code on the web reacting to PR activity: rejected, it covers only PRs opened from cloud sessions and cannot detect conflicts.
  • Tell agent from human comments by time (anything after the PR left draft is human): rejected, the steward's own review round and overlapping sessions break the ordering; the trailer classifies each comment on its own.
  • Stacked PRs with gh stack: rejected for now, cloud sessions can push only to their own working branch, and squash-only merging plus the no-force-push rule makes stacks unmaintainable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:toolingRepo scripts, CI workflows, workspace configstatus:readySpec and fix direction are clear; an agent can pick it uptype:featureSomething Appduct should do that it does not today

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions