Skip to content

fix(github): answer a control command no deployment claimed - #1309

Draft
aparajon wants to merge 2 commits into
mainfrom
armand/unowned-control-command-reply
Draft

aparajon wants to merge 2 commits into
mainfrom
armand/unowned-control-command-reply

Conversation

@aparajon

@aparajon aparajon commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

On a repository several SchemaBot deployments serve, an unscoped control command reaches all of them, and each one that does not store the named apply stays quiet so only the owner replies (AZ-5). That is right whenever an owner exists.

When none does — a mistyped identifier, one from another repository, or one a database engine reported rather than SchemaBot — every deployment defers to an owner that never speaks. The operator's command produces nothing at all, and a retry looks exactly the same, so the loop can run for a long time before anyone concludes the identifier itself was wrong.

Before — no deployment stores the apply, so no deployment answers:

  operator ──"stop apply-xxxx"──┐
                                │
              ┌─────────────────┼─────────────────┐
              ▼                 ▼                 ▼
        ┌──────────┐      ┌──────────┐      ┌──────────┐
        │ deploy A │      │ deploy B │      │ deploy C │
        │ no apply │      │ no apply │      │ no apply │
        └────┬─────┘      └────┬─────┘      └────┬─────┘
             │ silent          │ silent          │ silent
             └─────────────────┴─────────────────┘
                               │
                               ▼
                          (no reply)

After — the leader re-reads the acknowledgment reaction and answers if nothing claimed it:

  operator ──"stop apply-xxxx"──┐
                                │
              ┌─────────────────┼─────────────────┐
              ▼                 ▼                 ▼
        ┌──────────┐      ┌──────────┐      ┌──────────┐
        │ leader   │      │ deploy B │      │ deploy C │
        │ no apply │      │ no apply │      │ no apply │
        └────┬─────┘      └────┬─────┘      └────┬─────┘
             │ wait grace      │ silent          │ silent
             ▼
      read 👀 on comment
             │
     ┌───────┴────────┐
     │                │
  claimed         unclaimed
     │                │
     ▼                ▼
  stay silent    add 👀, then reply once
  (owner is
   answering)

The acknowledgment reaction is what resolves it: it is the one signal about the command that every deployment can read, and per UX-2 a deployment adds it only once it has decided the command is its work. The leader marks the comment as it answers, both because answering is acting on the command and so a redelivered webhook sees the claim and stays quiet rather than posting a second copy.

Only the leader does this — several participants replying is the duplicate noise fan-out exists to remove. The reply is operator visibility, never a gate: an unavailable server config, a client failure, or an unreadable reaction each leave the command exactly as silent as it is today, and say so in the logs.

The message names where identifiers that do resolve come from, since an engine's own apply identifier is the thing an operator is most likely to have pasted (UX-4).

Invariants: extends AZ-5's enforcement — the registry entry and its Enforced: line are updated in this PR to name the leader's follow-up alongside the existing unowned-command policy. Upholds UX-2 (the reaction still means a deployment has committed to acting; answering is the act) and UX-4. Nothing is weakened: no gate, lease, or apply state is read or written on this path.

Reply to a command no deployment claimed

⚠️ No Schema Change Matched This Command

Apply: apply-49ea5a453e9a4f18 | Environment: production
Requested by: @Someone

No SchemaBot on this repository is driving a schema change with this identifier, so start acted on nothing.

Apply identifiers appear on the schema change comments SchemaBot posts to this pull request, and in schemabot status -e production. Identifiers reported by a database engine are its own and are not accepted here.

This pull request was written by Claude Code (Claude Opus 5).

On a repository several SchemaBot deployments serve, an unscoped control
command reaches all of them and each one that does not store the named
apply stays quiet so only the owner replies (AZ-5). When no owner exists
— a mistyped identifier, one from another repository, or one a database
engine reported rather than SchemaBot — every deployment defers to an
owner that never speaks, and the operator's command produces nothing at
all. Retrying looks identical, so the loop can run for a long time.

The aggregate leader now closes the gap. It waits out a grace period,
re-reads the command comment's acknowledgment reaction — the one signal
about the command every deployment can read (UX-2) — and answers only
if nothing claimed it, marking the comment as it answers so a redelivery
stays quiet. The reply names where identifiers that do resolve come
from, since an engine's identifier is exactly what an operator is most
likely to have pasted (UX-4).

The follow-up is operator visibility, never a gate: an unavailable
config, a client failure, or an unreadable reaction each leave the
command as silent as it is without this path, and say so in the logs.
Participants stay silent throughout, since several of them replying is
the duplicate noise fan-out exists to avoid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 5, 2026 19:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The grace sleep currently runs inside a goSafe-tracked goroutine, which can unnecessarily extend shutdown/drain time on deploys by up to the grace duration.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR improves operator visibility for apply-scoped PR control commands (e.g., stop) on repositories served by multiple SchemaBot deployments by ensuring that someone replies when no deployment actually owns the referenced apply. It does this by having the aggregate leader re-check the command acknowledgment reaction after a grace period and posting a single “unclaimed command” reply if the command remained unclaimed, extending AZ-5’s enforcement as documented.

Changes:

  • Add aggregate-leader follow-up logic to answer apply-scoped control commands that no deployment claims (reaction-based detection after a grace period).
  • Add a new rendered GitHub comment template and tests for the unclaimed-command reply UX.
  • Add GitHub client support and metrics wiring for listing comment reactions; update invariants to reflect the new enforcement point.
File summaries
File Description
pkg/webhook/unclaimed_command.go Implements aggregate-leader grace-period follow-up and single-reply behavior for unclaimed apply-scoped commands.
pkg/webhook/unclaimed_command_test.go Adds coverage for leader/participant behavior and claimed vs unclaimed reaction cases.
pkg/webhook/templates/issue_comment.go Introduces the unclaimed-command reply template rendered into PR comments.
pkg/webhook/templates/issue_comment_test.go Verifies the rendered unclaimed-command reply content (UX-4 guidance included).
pkg/webhook/issue_comment.go Centralizes the acknowledgment reaction string as commandAcknowledgmentReaction and documents its meaning (UX-2).
pkg/webhook/handler.go Adds a test seam field to override the unclaimed-command grace duration.
pkg/webhook/control.go Hooks the unclaimed-command leader follow-up into the “apply not found” silent fan-out path.
pkg/metrics/metrics.go Adds a new GitHub operation constant for listing comment reactions.
pkg/github/rate_limit_metrics.go Maps GET comment-reactions endpoint to the new metrics operation for rate-limit attribution.
pkg/github/client.go Adds CommentHasReaction helper to cheaply detect whether a comment has a given reaction.
docs/invariants.md Updates AZ-5 to include the leader’s unclaimed-command follow-up as an explicit enforcement point.
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/webhook/unclaimed_command.go Outdated
… in it

The leader waited out the grace inside tracked webhook work, so a shutdown
drain sat through the whole period before finishing — for a reply that is
operator visibility and never a gate. The wait is now a timer and only the
answer is tracked work, which is the late-timer class the handler already
uses for the delayed aggregate re-fold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants