Skip to content

fix(kix): continue a retried job from its published branch - #171

Merged
kixelated merged 5 commits into
mainfrom
quest/m0/kix/retry
Oct 21, 2026
Merged

kixelated merged 5 commits into
mainfrom
quest/m0/kix/retry

Conversation

@kixelated

@kixelated kixelated commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Completes quest/m0/kix/retry.md.

Problem

"Try again" on a failed plan, start, or bootstrap job creates a new job with head unset. If the failed job had already published, the retry's first publication paused with "already has the branch … Delete it on GitHub to start over".

Change

store.createJob (cloudflare/src/jobs/store.ts): when the target's latest job failed on the same branch, the new job starts from that job's recorded head, pending/pendingAt, and pull, the same state a reassigned attempt resumes from. Iterations still start from the PR head. The existing fence and branch checks in publish.ts are unchanged, so a branch moved outside kix since that head still pauses on the existing "changed outside this job" decision.

A failed start job that already opened its pull request occupies its quest, so the inbox and the jobs route would refuse the retry. startable (cloudflare/src/github/state.ts) lets a start through when the quest is ready and its only occupant is the open pull request recorded on the target's latest failed job; any other claim still blocks.

Docs in cloudflare/README.md and cloudflare/GITHUB.md mention retries alongside reassignment. The quest file and its entry in the kix epic are removed.

Decisions (made unattended, recommended option)

  • Only a failed latest job is continued. Per the quest. A retry after a completed or cancelled job, or after an older failure, starts fresh as before.
  • The pull request carries over too. Without it the retry's delivery would fail: deliver ignores PRs opened before the job was created, and GitHub refuses a second PR from the same branch. If the maintainer closed the failed job's PR, the retry is refused with "reopen it on GitHub", the same rule a single job follows.
  • A pending branch update carries over. A write that was still in flight when the job failed gets adopted when it lands. It is not mistaken for an outside change.

Validation

  • New tests in cloudflare/test/publish.test.ts cover a retry after a published checkpoint (continues the branch and the same PR), a retry with nothing published (fresh branch), and a branch moved outside kix (pauses on the decision without overwriting). Two of them fail against the previous createJob.
  • A test in cloudflare/test/inbox.test.ts covers retrying a quest claimed by its failed job's own pull request, and refusing it when another pull request claims it.
  • just check and just test pass in the Nix shell.

Suggested follow-ups

  • If a maintainer deleted the branch by hand after a failure (the old advice), the retry now pauses as "changed outside this job" instead of starting fresh. Answering a publication decision only resumes the agent, which cannot clear a blocked branch. Recommend a /quest-plan follow-up: let the maintainer pick "start over" on a blocked-branch decision so kix resets the fork branch itself. That would also cover leftover branches from completed or cancelled work.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 21, 2026 11:57
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new plan, start, or bootstrap job whose target's latest job failed now
starts from the head, pending commit, and pull request that job recorded,
the way a reassigned attempt resumes. A branch moved outside kix since
still pauses on the existing publication decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review October 21, 2026 19:03
@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome: the quest is complete. A retry now continues the failed job's branch and PR, and a branch moved outside kix still pauses on the existing decision. just check and just test pass. Three routine decisions were made unattended and are listed in the description, along with one suggested follow-up (a 'start over' choice on blocked-branch decisions). Marked ready; no review requested.

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 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-21T19:31:14.869228Z 3bef3fb New commits
🔒 Security Review ✅ Completed 2026-10-21T19:30:51.713399Z 3bef3fb New commits
ℹ️ 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: 5b996d469d

ℹ️ 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".

SELECT ?,id,?,?,?,?,?,?,?,?,?,'queued',?,? FROM github_projects WHERE id=? AND state='enabled' AND ${pinnable}`,
`INSERT OR IGNORE INTO jobs(id,repository,workflow,target,revision,branch,head,pending,pendingAt,pull,actor,trusted,pinRunner,pinAccount,state,createdAt,updatedAt)
SELECT ?,p.id,?,?,?,?,COALESCE(?,f.head),f.pending,f.pendingAt,f.pull,?,?,?,?,'queued',?,? FROM github_projects p
LEFT JOIN jobs f ON ?<>'iterate' AND f.state='failed' AND f.branch=? AND f.id=(SELECT id FROM jobs WHERE repository=p.id AND target=? ORDER BY createdAt DESC,id LIMIT 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Permit retrying a quest claimed by its failed job

When a failed start job has already opened the carried pull request and that PR has been observed, its quest/... branch makes readState() remove the target from project.available (cloudflare/src/github/state.ts:247-253). The inbox then suppresses “Try again” in blocked(), and the jobs POST would also reject it in resolve() (cloudflare/src/inbox/page.tsx:258-261, cloudflare/src/jobs/routes.ts:181-186), so this new f.head/f.pull continuation is unreachable for published start jobs once occupancy has refreshed. Retry authorization needs to recognize when the claim belongs to the latest failed job rather than treating that job's own PR as unrelated.

Useful? React with 👍 / 👎.

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.

Agreed, fixed in 2509d21. startable in cloudflare/src/github/state.ts now lets a start through when the quest is ready and its only occupant is the open pull request recorded on the target's latest failed job. The inbox (blocked) and the jobs route (resolve, via store.failedPull) both use it. An upstream branch claim or any other pull request on the quest still blocks. A regression test in cloudflare/test/inbox.test.ts covers both cases.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 21, 2026 12:15
A failed start job that opened its pull request occupies its quest, so the
inbox hid "Try again" and the jobs route refused the retry. Both now treat
the target's latest failed job's open pull request as the one the retry
continues, while any other claim still blocks.

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

@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: 2509d21def

ℹ️ 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 on lines +639 to +644
export async function failedPull(db: D1Database, repository: number, target: string) {
const latest = await db
.prepare("SELECT state,pull FROM jobs WHERE repository=? AND target=? ORDER BY createdAt DESC,id LIMIT 1")
.bind(repository, target)
.first<Pick<Job, "state" | "pull">>();
return latest?.state === "failed" ? latest.pull : null;

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 Preserve unrecorded pull requests when authorizing retries

When GitHub successfully creates a pull request but the subsequent delivered() write is lost, the failed job retains pull = NULL even though reconciliation observes its open PR as a claim. Unlike the previously fixed recorded-PR case, failedPull() then returns null, so startable() rejects the retry and the job cannot use the existing delivery-reconciliation path. Authorizing a retry therefore needs to associate an observed PR on the failed job's branch even when its number was not recorded.

Useful? React with 👍 / 👎.

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.

Leaving this one as is. Two reasons:

  • Observations only record a pull request's headRefName, not its head repository. Matching on the branch name alone would also let a retry through when a contributor's fork PR on the same quest/... branch claims the quest, which is exactly the claim startable must respect.
  • Even if authorized, the retry could not adopt that PR. deliver only looks up unrecorded PRs created since the current job, so a recovered PR from the failed job would still fail delivery. Fixing that means widening the lookup to earlier jobs, which is a separate change.

The case needs GitHub to create the PR and then the following D1 write to be lost, and the job to fail before its next publication re-finds it. The PR is still open and offered in the inbox as "Iterate on this PR", so the work is not stranded. I will suggest a follow-up quest to record a delivery before or with the creation, or to match observed PRs by head repository, rather than widen this PR.

(Written by Claude Opus 5.5)

# Conflicts:
#	quest/m0/kix/README.md
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary:

  • Continuing a retry from the failed job's branch, pending write, and pull request is unchanged from the original change.
  • Codex round 1 (P1, fixed in 2509d21): a failed start job that already opened its PR occupies its quest, so the inbox hid "Try again" and the jobs route refused the retry. startable in cloudflare/src/github/state.ts now lets a start through when the quest is ready and its only occupant is the open PR recorded on the target's latest failed job. Upstream branch claims and other PRs still block. A regression test in cloudflare/test/inbox.test.ts covers both cases.
  • Codex round 2 (P2, declined with a reply): authorizing a retry when the failed job's PR was created but its number never recorded. Observations lack the PR's head repository, and deliver only adopts PRs created since the current job, so this needs its own change. Suggested as a follow-up.
  • Merged main (including the answer flow from feat(kix): move to the next decision after an answer #173). The only conflict was the kix epic's Required list. Codex reviewed 3bef3fb with no findings, and CI is green.

(Written by Claude Opus 5.5)

@kixelated
kixelated merged commit 4942565 into main Oct 21, 2026
5 checks passed
kixelated added a commit that referenced this pull request Oct 21, 2026
…es its PR

A retried plan job continues the failed job's pull request (#171), but
the decision section rendered only the new job's answers, so the first
delivery after a retry erased the earlier rounds. Render the answers of
every job on the target that delivered the same pull request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Oct 21, 2026
…172)

* chore(quest): claim quest/m0/kix/paper-trail.md

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

* feat(kix): render planning decisions into PRs from recorded answers

The service now writes a planning pull request's decision section from the
job's answered decision questions, with a check mark on each choice and the
maintainer's own words. Permission requests and account or publication
questions stay out. Every prompt and answer appears only in a code span, so
GitHub links no mention, reference, URL, or closing keyword in them. Each
delivery re-renders the section and drops any the agent wrote, and planning
assignments tell the agent not to write the trail.

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

* fix(kix): keep a failed planning job's decisions when a retry continues its PR

A retried plan job continues the failed job's pull request (#171), but
the decision section rendered only the new job's answers, so the first
delivery after a retry erased the earlier rounds. Render the answers of
every job on the target that delivered the same pull request.

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

* fix(kix): end a decision section only at a marker on its own line

An untrusted prompt containing the end marker stopped the section match
inside its code span when an agent copied the published body back, so
the rest of the section leaked outside code spans. Code spans never start
a line, so the section now ends only at an end marker on its own line, or
at the end of the body. Also drop U+061C, U+200E, and U+200F with the
other bidirectional controls.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated deleted the quest/m0/kix/retry branch October 21, 2026 21:05
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