Repository navigation
chore: Duplicate-PR guard conflates "exists" with "correct" — blocks re-review of a wrong-content PR indefinitely - #628
Merged
getappz merged 1 commit intoAug 28, 2026
Conversation
…self-repair's own PR (via persisted metadata.pr.number) and stale CI-red duplicates (>3d unmerged+failing); tests 67/67, build+clippy clean; not yet committed. Agentflare-Agent: claude-code_2-1-246_agent Agentflare-Branch: task/192-duplicate-pr-guard-conflates-exists-with Agentflare-Item: 192-duplicate-pr-guard-conflates-exists-with
|
Warning Review limit reachedNext included review available in 21 minutes. View limit detailsLimit details: You’ve used all 2 included reviews currently available. Your 56 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
Comment |
getappz
added a commit
that referenced
this pull request
Sep 22, 2026
* fix(mcp): reject plan_required with no assignee_agent and no plan item(action="create"/"update") let a caller set plan_required: true (or a non-empty plan_approver) with neither an assignee_agent nor a submitted plan, producing an item no agent can ever claim to write a plan for and no human has anything to review yet. Item #628 hit exactly this: it sat inert for ~40 minutes, gated every supervisor tick with no comment explaining why. validate_plan_gate_claimable now rejects that specific combination on create/update while leaving the normal gate-then-plan-later flow (gate with an assignee, no plan yet) and the plan-up-front flow (no assignee, plan_asset_id provided) untouched. Also: the supervisor's plan-gate skip was logged server-side every tick but never posted to the item's own comment thread, unlike the no-assignee skip which does post one. It now posts once per gate (mirroring first_time_gated's existing pattern) so the item's comment thread explains why it's stuck. Agentflare-Agent: claude-code Agentflare-Branch: task/629-item-create-update-lets-plan-required-tr Agentflare-Item: 629 Agentflare-Session: bfffbfdb-38b6-4dfc-b22e-6e99e3f7172d * fix(test): pin owner identity in end_to_end_plan_gate_blocks_then_unblocks_claim item 629's new claimability check requires the test's create call to set assignee_agent="claude-code", but the test's final claim() relied on ambient agent-detection (owner_id() -> agent_detector::agent_name()) to match that same identity. That resolves to "claude-code" only when actually running inside Claude Code, so the test passed locally but failed deterministically on CI's bare Ubuntu runner (falls back to "cli", tripping claim()'s BlockedByAssignee handoff freeze). Wrap the test body in with_owner_override("claude-code:test", ...) so it no longer depends on the environment it happens to run in. Agentflare-Agent: claude-code Agentflare-Branch: task/629-item-create-update-lets-plan-required-tr Agentflare-Item: 629 Agentflare-Session: ceb8a60f-fbb3-4fb4-9c7b-92a9d64885c8 * fix(test): pin owner identity in item_claim_blocked_by_plan too Same bug as end_to_end_plan_gate_blocks_then_unblocks_claim: this test's create call also needed assignee_agent="claude-code" added (to satisfy plan_required's new claimability check), which made its final claim() depend on ambient agent-detection matching that identity -- true only when actually running inside Claude Code. Wrap it in the same with_owner_override("claude-code:test", ...) pattern. Agentflare-Agent: claude-code Agentflare-Branch: task/629-item-create-update-lets-plan-required-tr Agentflare-Item: 629 Agentflare-Session: ceb8a60f-fbb3-4fb4-9c7b-92a9d64885c8
4 tasks done
getappz
added a commit
that referenced
this pull request
Sep 25, 2026
…inned MCP merges (#809) * fix(worktree): self-heal wedged task worktrees and stop clobbering remote work Multi-agent runs kept leaving task worktrees in states nothing recovered from automatically. create_worktree/rebase/commit/push now heal them: - Detached HEAD / other branch: a clean checkout is switched back onto its task branch in place (fast-forwarding over detached commits, or pinning diverged ones under refs/agentflare/rescue/) instead of `worktree add` failing on the occupied path every redispatch. - Interrupted git: abandoned index.lock (>10 min, or ours after a timeout kill) is removed and a half-finished rebase aborted; the abort result is checked instead of discarded. - Half-created checkout (worktree add killed mid-populate, "initializing" lock left) is recreated rather than reused and committed as mass deletions. - Broken .git pointer: repaired, else snapshotted and cleared; commit and cleanup refuse to run in it (git would act on the main repo). - push uses --force-if-includes and, on rejection, integrates origin/<branch> and retries once, so human/update-branch commits on the PR branch are kept instead of dropped or rejected forever. - worktree add retries recompute -b after a partial success, match real ref/index lock errors, and run under a timeout. - branch_diverged checks against origin/<target> as well as the (never pulled) local target. - gc_orphans drops only the removed path's registration instead of a repo-wide `worktree prune`; relative gitdir pointers are resolved; the branch-scoped cleaner honors locked registrations. - cleanup pins detached-only commits before removal; squash_since refuses a base that is no longer an ancestor of HEAD. * fix(git): run daemon git non-interactively in C locale; lock claimed worktrees - Every git spawn now gets LC_MESSAGES=C/LANGUAGE= (LC_ALL removed): lock-race retries, push-rejection recovery and the force-if-includes fallback all classify git's English stderr, and silently did nothing on a non-English workstation. - GIT_TERMINAL_PROMPT=0 / GCM_INTERACTIVE=never: a credential prompt fails fast as an auth error instead of hanging until a timeout, or forever for calls without one. - GIT_OPTIONAL_LOCKS=0: the daemon's read-only status polling no longer takes index.lock in a worktree an agent is committing in. - Claimed worktrees are `git worktree lock`ed with an agentflare reason so an outside `worktree prune`/gc can't drop their registration; our own cleaners unlock/remove only registrations carrying that reason. - A child item stops targeting its parent's branch once the parent is completed or cancelled, instead of stacking on an already-landed (squash-merged) branch and conflicting on every rebase. - A worktree whose changes can't be auto-committed because its checkout is broken now fails `done` loudly (item #92) instead of reading as "nothing to commit". * fix(claims): atomic, state-gated item transitions; stop double dispatch - mark_completed / mark_in_review / promote / redispatch / release use IMMEDIATE transactions: DEFERRED read-then-write failed instantly with SQLITE_BUSY_SNAPSHOT under concurrent writers, erroring `done` after its PR was already open. - mark_in_review / mark_completed only move items in the started or in_review group, so a late finalize can't revive a cancelled, completed or redispatched item. - promote_in_review_to_completed_if_pr compare-and-sets on the PR number that was verified merged. - acquire_if_stale_only / ClaimLedger::acquire_existing: a steal never re-creates a released claim row, so a reassigned job can't take the item back and publish. - Supervisor dispatch decision waits on ANY live claim (the item's own assignee session included -- it was invisible, causing dispatch -> Held -> failure-cap -> needs-manual-dispatch) using the real 4h item claim TTL instead of the 30 min repo-claim TTL; same TTL fix in handoff. New tier waits while blocking dependencies are incomplete. - Fallback claim owner is <agent>:<pid>-<random>, unique per process, instead of <agent>:<pid> which collided across sandboxes/machines. - `git worktree audit/doctor`: claimed_sequence_ids and item_state_groups read backend.db (they read the wrong database and always came back empty), are scoped to the repo's own project, and the claim guard fails closed. * fix(restart): executor leases for workflow runs; reap orphaned agents; single daemon After a daemon crash/stop/upgrade work could be double-run, waited on forever, or left with an old agent still editing the worktree. - flare-workflow: runs carry an executor lease (host:pid:nonce, renewed every TTL/4, stored in state_json so older binaries still load rows). recover() skips runs another live process is driving, takes over stale ones (a same-host lease with a dead pid is stale immediately), and isolates per-run failures instead of aborting all recovery on one bad row. New resume_run / is_driving / is_leased_elsewhere / recover_with and patch_run_data (a context patch that survives an in-flight step's write-back). - agentflare-jobs: a watchdog timeout now flags the attempt cancelled so its agent children and run actually stop before the retry, instead of abandoning a live thread that keeps heartbeating the claim. - Launched agents record pid + start-time under agent-pids/<worktree>; orphan reconcile kills that recorded group (not only `pgrep -f`, which never matched on macOS/Windows where the path isn't in argv) and, on Linux, processes whose cwd is inside the worktree -- except ones with a controlling terminal, i.e. a human's shell. - orphan reconcile: the terminal-failure label swap runs in one BEGIN IMMEDIATE transaction (retried), so an item can no longer end up with neither `dispatched` nor `ready-for-work` and vanish from dispatch; restore_ready_for_work uses IMMEDIATE too. - daemon: the lock file is never deleted (deleting it let two processes both "hold" it) and a lifetime flock is the liveness test. * fix(completion): cross-project review sweep, closed PRs, no-PR repos, atomic metadata - Review sweep uses a project-scoped MCP view per batch: items outside the daemon's cwd project could never be promoted and sat in_review forever with the lease held. - PrCiStatus::Closed (REST + GraphQL): a PR closed without merging requeues its item (redispatch to backlog, lease released, metadata.pr cleared, one comment) instead of leaving it in_review forever; a closed own PR is never reused. - push_and_open_pr returns PrOutcome; "no PR possible by configuration" (non-GitHub/no origin/no credentials) completes the item with metadata.no_pr instead of failing `done` forever; transient failures still error so finalize retries. - find_existing queries head=<owner>:<branch> (one request, no unbounded pagination), prefers metadata.pr.number, and a lookup error never falls through to creating a duplicate PR. - Metadata writes (PR identity, run id, item_update) re-read, merge and write in one IMMEDIATE transaction instead of clobbering concurrent writes from a snapshot. - Finalize treats item_done's "unchanged" as a failure (re-armed by the terminal-failure hook) rather than reporting success. - item_claim rolls back the claim and prior state when the worktree can't be created (non-retryable); item_done refuses completed or cancelled items; discovery resumes its own earlier PR claim. - Supervisor: one project's DB error or missing folder no longer aborts the discovery tick for every project; per-project cap on active work jobs when several projects have ready items; job_in_flight uses an EXISTS query instead of the 100 newest jobs; a failed boot recover() retries every 60s instead of leaving dispatch off until restart. - Restart wiring: an attached run adopts the current owner (finalize was refused after a restart and all work redone), stale runs are taken over instead of waited on forever, a lost claim or cancelled job stops the run, and boot recovery skips runs whose item is already finished. - done/release steal only an existing stale claim: a job that lost its item to a reassignment can't re-mint the lease and publish. promote compare-and-sets on the PR number verified merged. * feat(sessions): live agent-session registry in agentflare.db One row per running agent session (interactive or daemon job), keyed by its claim owner. Liveness is exact by pid on this host (so an idle but open session stays live) and by last_seen_at elsewhere. Foundation for releasing dead sessions' claims in minutes instead of after the claim TTL, and for inter-agent messaging. * chore(claude): SessionStart hook installs lean-ctx and prefetches crates Web sessions get lean-ctx (via cargo install -- the npm installer's GitHub releases download is blocked by the sandbox proxy), onboarded without shell aliases and with rustfmt/python3/timeout on its shell allowlist, plus cargo fetch so tests and clippy start warm. * feat: claim liveness, job controls, agent failover and realtime messaging Claim liveness (src/claim_liveness.rs): every discovery tick judges each item claim's owner -- a daemon job by its job row, a session by the session registry (exact pid on this host), a pid-form owner by pid plus claim silence -- and releases a dead owner's claim after two consecutive dead verdicts, commenting and re-arming the item. Claims of crashed interactive sessions or killed `agentflare work` processes are freed in about a minute instead of after the 4h TTL. release/done take over a dead owner's claim immediately. Headless runs register their session. Job controls (src/job_controls.rs, flare-workflow, agentflare-jobs): - cancel really stops: cancels the item's run and queued/running jobs, kills the agent, releases the claim, and is never retried/re-queued. - pause/resume: the engine stops at the next step boundary (the stopped step is not journaled as a failure) and resumes the same run in the same worktree; a `paused` label holds the item meanwhile. - `agentflare job cancel|pause|resume`, `agentflare item cancel|pause|resume|redispatch [--agent]`, MCP item actions, dashboard POST /api/jobs/{id}/cancel and /api/items/{id}/{action}, chat /cancel /pause /resume /redispatch. - handoff of a claimed item to another agent releases the old claim and cancels its jobs. Agent failover (src/auth_runner_classify.rs, src/quota/failover.rs, src/cli/work_failover.rs, agent-registry): agent failures are classified (short rate limit, long rate limit, credit exhausted, usage window with parsed reset time, auth expired). Credit/window exhaustion and long rate limits cool the agent down until its reset and move the item to the next available agent immediately, without charging the item's dispatch failure ceiling; with no alternative the retry is scheduled at the reset time. Configurable via AGENTFLARE_FAILOVER, [failover] in config.toml, and per-item allowed_agents / failover:false. A hung agent whose output only repeats while its worktree doesn't change is killed after 45 min (AGENTFLARE_STALL_WINDOW_SECS); repeated mid-run exhaustion errors kill it early into failover. Realtime inter-agent messaging (src/messages.rs, src/hook_messages.rs): MCP tool `message` (send/list/inbox/read/whoami), CLI `agentflare message send|list|inbox|read|whoami|watch`, dashboard /api/sessions, /api/messages and an SSE stream, chat /msg. Addresses: session key or name, item:<id>, agent:<name>, *. Claude Code receives messages on its next tool call via PreToolUse additionalContext and a Stop hook keeps it from going idle with mail waiting; other MCP hosts get them piggybacked on agentflare tool results; headless runs drain them between turns. Bodies are wrapped as untrusted peer input in <agentflare-message> envelopes that a body cannot forge or close. * refactor: cut boilerplate in the item/pipeline flow; back under LOC gate Behavior-preserving: - AgentflareMcp::post_item_comment replaces 11 copies of the best-effort `comment_impl(CommentRequest{action:"create",..})` call. - metadata_object() replaces the copied coerce-metadata-to-object chain (merge_item_metadata, handoff merges, persist_comment_cursor). - item.rs: require_id / internal_err replace 13 id preambles and 12 internal-error mappings; the progress sender is fetched once instead of calling create_worktree / push_and_open_pr through two branches. - claims::ttl_secs delegates to the backend's identical default; wrapper tests that duplicated db-kit's ClaimLedger tests are dropped. - groom/standup/health handlers move verbatim to item_reports.rs and the finalize step to work_item_pipeline/finalize.rs: item.rs 3113 -> 2633 and work_item_pipeline.rs 1749 -> 1488 lines, both back under scripts/loc-gate.sh limits. * fix(github): pin merges to the checked SHA; honor statuses, required checks, reviews, rate limits - merge / update-branch send the head SHA the CI verdict was computed on (merge "sha", update-branch expected_head_sha); a head that moved in between is skipped for the tick instead of merging unverified code. - CI status counts legacy commit statuses (Jenkins/CircleCI/Vercel-style StatusContext, REST /commits/{sha}/status) alongside check runs, and decides on required contexts only when the repo marks any required; statusCheckRollup is the fallback. A clean, mergeable PR with no CI at all is Passing instead of Pending forever. - New PrCiStatus::AwaitingReview (blocked + REVIEW_REQUIRED / CHANGES_REQUESTED with green checks) takes the approval-gate path, so the human is actually pinged, with no merge attempt. - Secondary rate limits (403/429 with retry-after or "secondary rate limit"/"abuse") map to RateLimited instead of a token-scope error; a shared per-host backoff deadline short-circuits calls until it passes; mutating requests are serialized >= 1s apart; GraphQL RATE_LIMITED errors map to RateLimited. - The gh/vault token is resolved once per process and refreshed on 401. - After a merged item is promoted, its remote head branch is deleted unless the repo auto-deletes, it is the default/protected branch, or the head lives in a fork. - PRs for items bridged from a GitHub issue include "Closes #N" (or owner/repo#N across repos). * refactor: split supervisor, its tests and dashboard server back under the LOC gate Pure moves, no behavior change: the PR merge/promotion/closed-PR block to supervisor/merge.rs, the auto-merge test group to supervisor/tests/auto_merge_tests.rs, and work-dispatch boot/recovery retry to dashboard/dispatch_boot.rs (supervisor.rs 2888 -> 2472, supervisor_tests.rs 2810 -> 2549, server.rs 1550 -> 1463). * refactor: split flare-git-core worktree and flare-workflow engine under the LOC gate Pure moves, no behavior change: worktree self-heal and push-lease helpers to worktree_heal.rs, subprocess timeout/kill to worktree_process.rs, orphan audit/gc to worktree_orphans.rs, this branch's heal tests to worktree_heal_tests.rs; engine executor-lease / recovery to engine_lease.rs and pause/resume/cancel to engine_control.rs. Public paths are unchanged via re-exports (worktree.rs 1888 -> 1214, worktree_tests.rs 1839 -> 1461, engine.rs 1873 -> 1434). * fix(pipeline): a cancelled run always reports as a deliberate stop An operator cancel marks the workflow run Cancelled before it flags the job, so a waiter polling in between reported a generic failure (flaky pause_tests::cancel_stops_the_running_job_and_its_run_for_good under load). A Cancelled run needs no job-flag check. * fix(claude): SessionStart hook downloads prebuilt lean-ctx instead of compiling The hook ran `cargo install lean-ctx --locked`, which on the 4-core web sandbox compiles for well over 10 minutes and hit Claude Code's 600s SessionStart timeout on both startup and resume, so it never reached `lean-ctx onboard` and sessions started without lean-ctx at all. Install the prebuilt release tarball from the fixed github.com/yvgude/lean-ctx/releases/latest/download/<asset> URL instead, verified against the release's SHA256SUMS. That path needs no GitHub API call (api.github.com answers 403 from the sandbox's shared egress IP, which is also why `npm install -g lean-ctx-bin` fails there) and takes seconds. `cargo install` stays as the fallback, and settings.json raises the hook timeout to 1800s so the fallback can finish if it is ever needed. LEAN_CTX_VERSION pins a release; unset tracks latest. The binary lands in ~/.local/bin, which the hook also exports via CLAUDE_ENV_FILE. Validated in the sandbox: full hook run (download, checksum, onboard, allowlist, cargo fetch) completes in ~5s. * fix: address review findings on the pipeline hardening PR Workflow engine / jobs: - start_workflow claims `driving` before save(), so an in-process recover() can't start a second driver for a brand-new run. - A lease takeover marks the run superseded: the old driver stops at the next check without writing; terminal writes are refused while another executor holds a fresh lease. cancel only moves unsettled runs. - A paused/superseded driver drains in-flight parallel steps before letting go, so resume can't relaunch a sibling that is still running. - request_cancel refuses running subprocess jobs (NotCancellable) instead of reporting success for a job it can't stop. - An abandoned attempt that finishes Ok during the grace period is recorded as completed, not failed and retried. Git / GitHub: - Integrating commits pushed to the PR branch by someone else merges origin/<branch> instead of rebasing onto it (which replayed new target commits as the PR's own). - audit_orphans skips a task dir that isn't its own checkout. - Merged-branch deletion keeps a branch whose tip moved past the merged head (BranchCleanup::Advanced). - Rate-limit backoff is tracked separately for anonymous and authenticated requests; GraphQL RATE_LIMITED arms the backoff and stops the chunked batch. - Auth errors are logged as a fixed string (GitHubError::log_safe). - SessionStart hook path is quoted. Messaging / sessions / failover: - `message watch` and the SSE stream requeue a taken message whose print/send failed instead of losing the rest of the batch. - Envelope-tag escaping is case- and whitespace-insensitive. - The MCP server's provisional session row is deleted, not ended, so its live claims aren't judged dead. - Session host identity uses the kernel hostname (same for daemons and shells), then the machine id, before "localhost". - A typo'd failover allow-list disables failover instead of widening to any agent; the agent-wide "no alternative" cache is only armed for unrestricted items; short rate-limit retries post the neutral agent-unavailable marker; a stale cancelled run id no longer makes a fresh dispatch's setup error read as a cancel; handoff compares canonical agent names. * fix(worktree): match stale registrations by canonical parent, not raw path Windows CI: git records a worktree's gitdir under the long path (C:/Users/runneradmin/...) while the caller can hold the 8.3 short-name (RUNNER~1) spelling of the same directory, so the path-scoped registration cleaner never matched and left a prunable registration behind after cleanup (which also blocks a later re-claim). Compare via the canonicalized parent directory, which still exists after the checkout is removed, case-insensitively on Windows. * docs(audits): Claude Code feature audit and gap analysis Audit of every way agentflare uses Claude Code (host instrumentation via init, headless worker via work/run/workflows/apps, and the agent-agnostic registry) against Claude Code's full documented surface: CLI flags, settings keys, all 33 hook events and 5 hook types, memory, skills, subagents, MCP, plugins, sessions, cloud integrations and the Agent SDK. Findings are ranked P0-P2 with file:line evidence, a per-feature status matrix, the current per-agent capability table, a list of Claude-Code- style features worth universalising with the agentflare mechanism each maps onto, and a roadmap. Two bugs surfaced by the audit are recorded for follow-up rather than fixed here: `agentflare work` passes both `--output-format json` and `--output-format stream-json`, which makes the whole-stdout JSON parse fail and silently drops session_id and cost (so SDD-loop `--resume` never fires in real dispatch); and `agentflare run --mode` forwards `--mode`, which is not a Claude Code flag (`--permission-mode` is). Force-added under docs/ like docs/superpowers/plans, since /docs/ is gitignored at the root. * fix: propagate lean-ctx prebuilt install failures and escape table pipes Steps inside install_lean_ctx_prebuilt now return explicitly, since set -e is suppressed under 'if !', so a failed download/extract/install falls back to cargo. Pipes inside code spans in the gap-analysis tables are escaped so the rows render with the right column count. * fix(sessions): qualify host identity with the machine id Two machines sharing a hostname and a db resolved to the same host, so liveness_of checked one machine's pids against the other's. The host is now <hostname>@<machine-id> when both are available. * fix(launch): stop emitting two --output-format flags; add per-role specs for SDD dispatch The audit (docs/audits/2026-09-24) found that `agentflare work` launched Claude Code with `--output-format json` (from run_headless_impl when a structured reply was requested) followed by `--output-format stream-json --verbose` (from build_extra_args). The last flag wins, stdout became a multi-line stream-json transcript, `parse_json_reply` failed to parse it as one object, and every dispatch silently lost its session_id and cost — so the SDD loop's `--resume` between fix rounds never fired in real runs. - `headless_full_args` skips the registry's JSON-output flags whenever the caller already pins `--output-format` (any spelling, incl. `--stream-json`). - `parse_json_reply` falls back to the final line when the whole stdout is not a single JSON object, so both output shapes yield session and cost. - `agentflare run --mode` forwarded a bare `--mode`, which Claude Code rejects; `agent_registry::mode_flag` maps it to `--permission-mode` for Claude Code and keeps the pass-through for other agents. Roadmap P0 item 2, first slice: `agent_registry::role` introduces `RoleSpec` (system prompt, allowed/disallowed tools, permission mode) and `compile_role(agent, &spec)`. Claude Code compiles to `--append-system-prompt`, `--allowedTools`, `--disallowedTools` and `--permission-mode`; every other agent gets an empty argv and the identity folded into the user prompt via `prompt_with_system_fallback`, never an unconfirmed flag. The SDD loop (`work_item_pipeline/roles.rs`) now gives each dispatch a role: implementer keeps every tool, the analyst and the reviewers lose the editing tools (Bash stays for verification), the judge also loses the shell. Resume args still lead the argv; a mid-flight correction still leads the prompt. Docs: README and AGENTS.md no longer describe `optimize context` as PreCompact-driven compaction (the hook is inert); the audit's roadmap and bug list record what landed here. Verified: agent-registry tests (68), targeted agentflare bin tests for agent_launch/work_item_pipeline/cli::work (258), `cargo clippy` with the CI flags on both crates, `cargo fmt --check`. * feat(mcp): pin the pr_merge action to the head SHA the caller last saw The MCP flare_git pr_merge action merged whatever the PR head was at the moment of the call, so a commit pushed after the caller judged CI green rode along unchecked. It now goes through pulls::merge_at_head like the supervisor's own auto-merge: a new head_sha parameter pins the merge to the head the caller saw, and without one the current head is read first and pinned, closing the read-to-merge window. GitHub's 409 on a moved head is reported as an invalid-params error naming both SHAs and the head_sha to retry with. pr_get and pr_status now report head_sha so a caller has the value to pass back. * fix(supervisor): approval card names a GitHub review as the blocker, not the label When branch protection held a CI-green PR for a review (PrCiStatus:: AwaitingReview), the Telegram card still said the PR was mergeable and awaiting status:pr:approved, and a human tapping Approve got nothing: the label cannot merge a review-blocked PR. The card now says GitHub is blocking the merge until a review lands, whether an approving review is required or a reviewer requested changes (AwaitingReview and CiGreenMerge::BlockedOnReview now carry changes_requested), links the PR's review, and keeps the Approve button only while the label is also missing. The review card has its own once-per-item key so the plain label card is still sent once the review lands with the label absent. * feat(supervisor): arm GitHub's native auto-merge and support merge queues Once an item's PR carries the approval label and its CodeRabbit findings are clear, the sweep now calls enablePullRequestAutoMerge (pinned to the head it judged with expectedHeadOid) when the repo allows auto-merge, so GitHub itself merges the PR the moment every requirement holds: this is what lets a review-blocked PR merge as soon as the review lands, and the only way onto a merge queue. The direct merge_if_approved path stays as the fallback when the repo has auto-merge off or GitHub refuses to arm it (a PR it could merge right now), and promotion of merged items stays in the sweep. Auto-merge is disarmed again when findings appear or the label is removed, keeping item #628's invariant. The batch GraphQL query now also fetches the PR node id, autoMergeRequest and the merge-queue flags; a base branch with a merge queue never reads "clean", so green required checks on a queue-enabled branch count as Passing instead of Pending forever, and an enqueued PR waits. The merge method comes from the repo's allowed methods (GET /repos, cached with repos::settings: squash, then merge, then rebase) instead of a hard-coded squash. GraphQL mutations now get the same write spacing as REST writes. CI gains a merge_group trigger so a queue can run the required checks. * feat(github): conditional GETs with a bounded per-host ETag cache Every GET now remembers the response's ETag and body per URL and representation (Accept header), sends If-None-Match on the next request for the same URL, and serves the remembered body when GitHub answers 304. A 304 costs no primary rate-limit budget, so polling unchanged PRs, issues and check runs every sweep tick no longer eats the 5,000/hour allowance. The cache lives with the per-host throttle state (so mock servers in tests never share entries), is capped at 1024 entries evicted oldest-first and 512 KiB per body, and drops an entry whenever a response comes back without a validator. The secondary-rate-limit backoff still refuses calls before any network or cache lookup, since a cached body is only known current once the server has confirmed it, and the 401 token refresh retries through the same conditional path. The test mock server now records request headers so the conditional headers can be asserted. * feat(worktree): open item PRs as drafts and mark them ready once the item is in review push_and_open_pr now opens every item PR as a draft, so CI starts but nobody is asked to review and nothing can merge until item_done has recorded the item as in_review; item_done then flips it ready for review (GraphQL markPullRequestReadyForReview, using the node id the create already returned) and records metadata.pr.ready. Repositories that refuse draft PRs (GitHub's 422) get an ordinary PR instead. Nothing stalls on a draft: the CI-status paths (REST and batched GraphQL, which now fetches isDraft) report a new PrCiStatus::Draft before any check state, and the review sweep re-runs the ready flip for an in-review item whose flip never landed. A draft the item already records as flipped was converted back by a human and is held: not re-flipped, not merged, and not self-repaired, since Draft is decided before Failing/Passing. Discovery already skipped drafts and keeps doing so. item status reports "draft"; the MCP pr_create action accepts draft=true. * fix(sessions): judge by last_seen when the host lacks a machine id A bare hostname can be shared by two machines, so a matching host only proves the pid is local when it carries the OS machine id. * fix(workflow,jobs): keep cancellation authoritative over settlement Queue::complete finishes a cancel-requested job as killed in the same UPDATE, so an executor returning Ok can't turn it into exited. Completed/Failed settlement and Waiting writes skip runs that are already terminal, and publish their event only when they wrote. WorkflowCancelled is published only by cancel_workflow_impl, once, when it changed the status. * fix(github): header-driven GraphQL rate-limit backoff and sha-guarded branch delete GraphQL RATE_LIMITED 200s now arm the host backoff from retry-after / x-ratelimit-reset via Client::graphql, and mutations are spaced like other writes. Merged head branches are deleted with updateRefs guarded by beforeOid; there is no unguarded REST fallback. * fix(messaging,claims): at-least-once SSE take, atomic identity adoption, fenced forced completion - release_claim disarms the guard only on an explicit released: true. - Provisional identity adoption reroutes mail and forgets the row in one IMMEDIATE transaction; a failure keeps the row for a retry. - SSE take=true takes one message per granted send permit and requeues in-flight ids when the client goes away. - Forced merged completion is fenced on the PR that was checked as merged (mark_completed_forced_if_pr). * fix(messages): keep taken SSE ids requeueable until the stream closes Being pulled off the channel doesn't prove the client received an event, so the take stream no longer forgets in-flight ids after an idle poll. message watch skips ids it already printed, so a reconnect after a requeue doesn't print a message twice. * fix(supervisor): only disarm auto-merge the sweep armed, and disarm it when the head moves Review findings on PR 809. The sweep now records what it arms (metadata.pr.auto_merge: the pinned head and the PR node id) and only ever disarms an auto-merge it armed itself, so a maintainer's own "Enable auto-merge" is left alone. A push from a write-access account keeps GitHub's auto-merge armed, so every tick first re-judges a recorded arming against the fresh snapshot, whatever the CI state: a moved head disarms it before the new, unjudged commits can merge, and an arming GitHub no longer reports is forgotten. Failing CI and a merge conflict disarm it too, ahead of any repair push. Also decodes a cached empty body served on 304 as Null, the same as the first time, and adds the docstrings CodeRabbit's coverage check counted as missing. * fix(supervisor): keep a failed disarm on record, and stamp judged heads for a merge-time gate Follow-up on the head-moved review thread. A disarm GitHub refused used to clear the sweep's arming record anyway, leaving nothing to retry; now the record stays until GitHub confirms the disarm. And because sweep-time reconciliation alone cannot stop GitHub merging a head that was pushed and went green between two ticks, the sweep now posts a per-commit status (context agentflare/judged) on the exact head it armed auto-merge on. A repo that requires that context in branch protection gets a merge-time gate: a later push is a new sha without the status, so GitHub waits for the sweep to judge it. Repos that don't require it see an informational status. * fix(supervisor): stamp the judged head before choosing the merge path, retry a missing stamp Follow-up on the judged-status review threads. The agentflare/judged status was posted only right after a fresh arming, so an arming from an earlier tick whose stamp failed, a review-blocked PR, or a direct merge under a repo that requires the context could sit unstamped. The stamp now runs first in merge_approved_pr, after a lookup of the head's statuses: posted when missing, skipped when present, and left for the next tick when the lookup itself fails. The context's docs now say it is not for branches that merge through a merge queue, where required statuses are evaluated on the queue's merge-group commit the sweep never sees; there the merge_group CI run is the gate. * fix(supervisor): arm GitHub auto-merge only behind a merge-time gate Auto-merge armed on a branch with no merge-time gate lets GitHub merge a head pushed and gone green between two sweep ticks that nobody judged for findings. Arm it only when something stops that: the base branch merges through a merge queue (the queue's own merge_group CI run is the gate, and a direct merge is refused there anyway), or the base branch's protection or rulesets require the agentflare/judged status the sweep stamps on every judged head. Otherwise the sweep merges directly, pinned to the judged head, as before; a review-blocked PR on such a branch waits for the review and merges on the next tick. repos::required_status_contexts reads branch protection and rulesets for a branch once per process (an unprotected or unreadable branch reads as "nothing required"). AutoMergeRef carries the PR's base branch and merge-queue flag from the same fetch as the CI verdict (GraphQL baseRefName/isMergeQueueEnabled, REST base.ref). * fix(github): do not cache a failed required-status-contexts read required_status_contexts cached its result even when the branch protection or rulesets read failed with a transient error (5xx, rate limit, 403). The branch then read as "nothing required" for the rest of the process, so auto_merge_gate stopped arming auto-merge on a branch that requires agentflare/judged until a restart. Cache only a complete read (both reads succeeded, or rulesets returned NotFound), like repos::settings; a failed read is retried on the next ask. --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item work (PR identity, task 0): fixed duplicate-PR guard to exclude self-repair's own PR (via persisted metadata.pr.number) and stale CI-red duplicates (>3d unmerged+failing); tests 67/67, build+clippy clean; not yet committed.
Opened by
claude-codeon flared:51bb8de6c33b for item #192 via agentflare.