Skip to content

fix(hold): keep quest:merge as the pick after a landing ends unmerged - #277

Closed
kixelated wants to merge 1 commit into
mainfrom
fix/keep-merge-pick
Closed

kixelated wants to merge 1 commit into
mainfrom
fix/keep-merge-pick

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Problem

When a PR left the merge queue unmerged (merge_conflict, failed_checks, or a quest land ejection), the CLI and the kix.quest bot removed quest:merge. That erased the maintainer's merge pick, so the next /quest-complete asked about the same PR again. In one moq session that happened to dozens of PRs.

Change

The label now stays on as the pick, which no session holds.

  • quest merge without a token takes over a pick: it removes the label and adds it back, getting a fresh token, so the PR is exclusively held again. quest iterate/ready refuse it with … is picked for merge …. The session that landed can still use its old token.
  • quest status prints a pick as pick quest:merge <token> <time> <url> <age> instead of hold.
  • quest land ejections keep the label.
  • The bot's releasePullRequest leaves the label on when the hold is spent, meaning a landing ended unmerged. A merge job that ends without landing (for example, stopped to ask) still removes the label. holdPullRequest already replaced a spent label.
  • /quest-complete dispatches /quest-merge for every pick without asking. /quest-merge says the label persists.
  • Removing the label withdraws the pick. The --help text and README document this.

Choices made without asking

  • All dequeue reasons keep the pick, including a person dequeuing. The CLI can't cheaply tell why a PR left the queue. To withdraw a pick, remove the label. The merge skill still has the agent report any reason other than a conflict or failed checks instead of landing again.
  • Only quest merge takes over a pick. Letting iterate/ready take it would silently drop the pick.

Checks

just check passes. just test passes: 620 CLI tests and 426 Worker tests. Two test/agent.test.ts cases fail when CLAUDE_CONFIG_DIR/QUEST_WORKTREE_HOME are set in the caller's environment; that happens on main too, and they pass with those variables unset.

Suggested follow-ups

  • The bot could dispatch a merge job for a parked pick itself; today the inbox just hides a picked PR.
  • test/agent.test.ts should clear CLAUDE_CONFIG_DIR and QUEST_WORKTREE_HOME from the environment it inherits.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

A merge queue dequeue (merge_conflict, failed_checks, a quest land ejection)
used to remove quest:merge, so the maintainer's merge pick was lost and the
next /quest-complete asked about the PR again.

The label now stays on as the pick, which no session holds:
- `quest merge` without a token takes it over with a fresh token; iterate and
  ready refuse it, naming the pick.
- `quest status` prints it as a `pick` record instead of `hold`.
- `quest land` ejections and the kix.quest bot leave the label on; the bot
  still removes it when a merge job ends without landing.
- /quest-complete dispatches /quest-merge for every `pick` without asking.
  Removing the label withdraws the pick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T01:11:09.119614Z e484346 PR opened
🔒 Security Review ✅ Completed 2026-10-30T01:11:17.419912Z e484346 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e4843464da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/hold.ts
Comment on lines +301 to +303
if (token === undefined && state === "merge" && spentMergeHold(snapshot)) {
await labels.remove(MERGE_LABEL);
snapshot = { ...snapshot, labels: snapshot.labels.filter((name) => name !== MERGE_LABEL) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh spent holds when reacquiring with an existing token

After a landing ends unmerged, reacquiring merge ownership with the original token skips this refresh and reaches the mine === label early return. The acquisition succeeds, but spentMergeHold remains true, so status still reports an unowned pick and another session can immediately take it over while the original session repairs the PR. This newly permits overlapping work and invalidates the first session's supposedly reacquired token. Refresh the spent hold on token-bearing reacquisition too, or refuse that acquisition and require a fresh take.

Useful? React with 👍 / 👎.

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit: e484346.

No new actionable defects found. Separating a persistent merge pick from an active session hold is a sensible direction. The CLI's fresh-token takeover, status classification, queue-ejection preservation, and bot's token-checked release of ended jobs are consistent with that distinction. The existing non-atomic label acquisition limitations are not resolved by this change.

Nonblocking follow-up: test refused takeover of a picked draft and consider performing refusal checks before removing its label, so an unsuccessful attempt preserves the pick.

GitHub-only static review of the diff, hold classification/acquisition, bot release/store lifecycle, skills, and test source. No CLI/Worker tests or live GitHub lifecycle experiments were run; reported check counts were not independently verified.

(Written by OpenAI)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by the host-land plan (#291). An ejection will now remove quest:merge, and a Codex fixer either requeues the PR or hands it back to the maintainer, so the label no longer needs to persist as the pick. Decided by the maintainer on 2026-10-30.

(written by Claude Opus 5.5)

@kixelated kixelated closed this Oct 30, 2026
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.

1 participant