Skip to content

feat(worker): make the decision inbox the signed-in home - #144

Merged
kixelated merged 15 commits into
mainfrom
quest/m0/kix/prompts
Oct 19, 2026
Merged

kixelated merged 15 commits into
mainfrom
quest/m0/kix/prompts

Conversation

@kixelated

@kixelated kixelated commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Implements quest/m0/kix/prompts.md (Make interactive decisions the main UI) and deletes the quest.

Problem

Signing in to kix.quest led to project setup screens. There was nowhere to answer an agent's question, notice a new issue or PR, or start ready work. The runner protocol also took an opaque prompt that the UI could not render.

Behavior

  • /inbox is the signed-in home. Sign-in now lands there by default, and the nav shows "Decisions" when you're signed in. The page shows one active decision thread first, then what's up next, what's in progress, your projects (refresh, start a planning interview, board, activity), and a collapsed list of skipped and finished items. It's laid out for a phone and uses plain forms with no JavaScript.
  • One item per job target (issues/1, pulls/2, a quest path). An issue, its planning job, and that job's questions stay one item. Webhook updates, duplicate deliveries, and reconnects never add a second prompt.
  • What each item offers:
    • Agent question: the options with an optional recommended mark, plus free text.
    • Account limit: pick one of the requester's accounts.
    • Failed job: try again or skip.
    • New PR: iterate, or review on GitHub.
    • New issue: plan it, or skip.
    • Ready quest: start it, or skip.
    • Running or queued job: stop it.
  • Actions are offered only where they would work. A paused project offers no new work. While observations are incomplete, an item offers only a refresh.
  • States are shown plainly: waiting for your decision, account limited, failed, queued, no runner online, runner offline (with the 2-minute move), working on a named runner, stopping.
  • Prompt contract (src/jobs/prompt.ts): { kind: "decision" | "permission", questions: [{ header?, question, options: [{ label, description?, recommended? }], multiple? }] }.
    • The runner protocol refuses a prompt the inbox can't render.
    • An answer is { answers: [{ choices, text? }] }, one per question, checked against the prompt.
    • Prompts render as escaped text, and the recommendation is never preselected.
    • Checkpoint summaries show as "Latest update", never as a prompt.
  • Decisions are bound to what was on screen. The job routes accept the inbox's form posts as well as JSON, and recheck maintainer authority each time.
    • A job request may carry the revision the maintainer saw, such as a PR head, and is refused once observations have moved past it.
    • Repeated or stale answers are refused, and the form shows a "Not recorded" page linking back to the inbox.
    • A missing flake leads to a "Start a bootstrap job" form; the JSON API still returns bootstrap: true.
  • Unread notifications in the app (inbox_items, migration 0010): each maintainer's read state is keyed by a version of the item (a question id, a GitHub changedAt, a hash of the quest text). A newer version shows as unread again. A skip lasts until the item changes, and a finished or stopped job sets its item aside the same way.
  • Submitted answers stay in the thread under "Earlier answers", so a refresh or reconnect keeps both the current question and past answers.

Validation

  • just check and just test pass inside nix develop after merging main: 227 CLI tests and 249 Worker tests.
  • New test/inbox.test.ts runs end to end through the Worker. It covers:
    • Sign-in landing on the inbox.
    • Issue, PR, and quest items in order.
    • Duplicate and repeated webhooks coalescing into one unread item.
    • A stale revision being refused.
    • Progress shown while no runner is online, then stopping and setting aside.
    • The bootstrap offer.
    • Answering a two-question prompt from the form (free text and multi-select), with escaped agent text and no preselection, and a repeat answer refused.
    • Choosing an account after a limit.
    • Skipping until an item changes.
    • A project whose maintainer lost authority being hidden.
    • A paused project keeping its questions but offering no new work.
  • New tests check prompt and answer validation. The job-route tests now share test/project-fixtures.ts, which also holds the signed webhook helper.
  • Checked in a real browser against a local Worker (Miniflare with local D1 and GitHub mocked), seeded with an agent question, an account limit, a failed job, queued and running jobs, PRs, issues including a pointer-only one, ready quests, and a paused project, at 375px and 1280px:
    • Sign-in lands on /inbox. There is no horizontal scroll at 375px, and long unbroken titles wrap.
    • Answered a two-question prompt (radio, checkbox, free text), chose an account after a limit, retried a failed job, and skipped a PR. Each was recorded and showed up under "Earlier answers", "In progress", or "Skipped and finished".
    • Fixed a defect found there: the PR meta line reused .entry-meta (grid-column: 2), which split the decision thread into two columns on wide screens. GitHub's check and review enums now read as plain words ("changes requested").
  • Not verified against live GitHub.

Review fixes

Four rounds of Codex code review findings (the fourth approved by the maintainer), each with a regression test in test/inbox.test.ts:

  • publication decisions from bot publication (feat(worker): publish agent work through the GitHub bot #145) render the refused paths or the blocked reason, and take a text reply from the form.
  • The inbox reads each target's latest job however old: every one awaiting a decision, plus any other whose target is still observed. An index on jobs(repository, target, createdAt) in 0010_inbox.sql backs the lookup.
  • Retry and start actions are built only when unblocked. They are not offered for a quest that is no longer ready, and a failed interview's retry keeps its plans/<id> target.
  • Quest links encode their path segments.
  • A publication reply is limited to 1,000 characters of text, matching the form, rather than by its JSON-encoded length.

Settled decisions

The maintainer accepted every recommendation:

  1. Projects covered. The inbox lists projects the viewer enrolled (enrolledBy). Co-maintainer membership is a separate follow-up.
  2. No merge action here. PR items offer "Review on GitHub". A merge bound to the PR head comes later through the bot publication quest.
  3. Skips last until the item's version changes.
  4. Queue order: agent questions oldest first, then failed jobs, new PRs, new issues newest first, then ready quests in quest ready order.
  5. plan and start stay bound to the default-branch revision; a moved main refuses the stale action.
  6. Answer shape stays { answers: [{ choices, text? }] } with the recommended option flag. The runner adapter maps Claude Code's AskUserQuestion onto it.

Planning

Batched inbox reads is added to m1 in this PR.

Retry from the branch is added to m0 in this PR, for the last Codex finding.

  • Retry finding: ✅ Merge and follow up (Recommended) / Fifth round
  • Retry fix: ✅ Continue from the published branch (Recommended) / Offer a reset / Fold into execution

Limitations and follow-ups

Follow-up quests are planned separately, not in this PR. #146 added webhook reconcile, inbox merge, and co-maintainer inbox. Merging main here drops this deleted quest from their Required lists.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 18, 2026 22:10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add /inbox: one active decision thread across a maintainer's enrolled projects, coalesced per job target, with agent questions, account-limit choices, failed jobs, new pull requests, new issues, and ready quests. Define the agent prompt contract (choices, recommendation, free text, permission vs decision) and validate prompts and answers. Job routes accept inbox form posts, bind decisions to the revision on screen, and refuse stale or repeated answers. Per-maintainer read state marks changed items unread and keeps skips until an item changes. Sign-in now lands on the inbox.

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

Copy link
Copy Markdown
Collaborator Author

Outcome of the quest-start run: the decision inbox is implemented and just check and just test pass in nix develop. It has not been checked in a browser or against live GitHub. This stays a draft until the six open decisions in the description are settled; nothing was merged. Suggested follow-ups for /quest-plan: inbox membership for co-maintainers, an explicit merge action bound to the PR head, reconciling in the background after webhooks, an unread badge in the nav, and a runner and account settings page.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 18, 2026 22:56
The PR meta line reused .entry-meta, whose grid-column: 2 split the thread
into two columns once there was room. Also show GitHub's check and review
enums as plain words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the deleted prompts quest from the follow-up quests #146 added, and
remove their now-empty Required sections.

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

chatgpt-codex-connector Bot commented Oct 19, 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-19T19:20:58.217601Z 6fe526c New commits
🔒 Security Review ✅ Completed 2026-10-19T19:21:41.020971Z 6fe526c 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.

@kixelated

Copy link
Copy Markdown
Collaborator Author

#147 merged into main and adds quest/m0/kix/harness.md and quest/m0/kix/answer-flow.md, which both list /quest/m0/kix/prompts.md under Required. Since this PR deletes prompts.md, it must merge main and drop that entry from both files, deleting each Required section once it is empty, so quest check stays green.

(Written by Claude Opus 5.5)

Brings in raw output retention (#142) and the harness and answer-flow
plans (#147). The inbox migration becomes 0009_inbox.sql after
0008_output.sql. The shared maintainerOf() fixture adopts main's
stricter GitHub mock, where only the signed-in user holds the maintain
role. harness.md and answer-flow.md drop this deleted quest from
Required, and activity.md drops both deleted quests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in bot publication (#145). The inbox migration becomes
0010_inbox.sql after 0009_publication.sql. merge.md and planning.md
drop the deleted publication and inbox quests from Required, and
merge.md's emptied section goes away.

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

Copy link
Copy Markdown
Collaborator Author

#150 merged quest/m0/kix/paper-trail.md, whose Required section lists /quest/m0/kix/prompts.md. This PR deletes that quest, so it must also drop that entry from quest/m0/kix/paper-trail.md. It is the only entry, so delete the whole ## Required section, or quest check will fail after merging main.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 19, 2026 10:59
#150 added paper-trail.md requiring /quest/m0/kix/prompts.md, which
this PR deletes. That was its only blocker, so the Required section
goes away.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

The earlier code review failed and posted nothing. Re-triggering at the maintainer's request.

(Written by Claude Opus 5.5)

@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: f778d7d319

ℹ️ 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 +313 to +319
{item.need === "answer" &&
(item.prompt ? (
<AgentForm item={item} prompt={item.prompt} />
) : (
<p role="alert">
The agent's question could not be read. Stop the job or wait for the runner to ask again.
</p>

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 Render publication questions before treating them as answers

When publication pauses a job because files are refused or the branch moved, the open question has kind === "publication". This path classifies it as need === "answer", but only agent questions populate item.prompt, so the page falls through to “could not be read” and offers no way to submit the publication decision. The job therefore remains stuck unless the maintainer manually uses the JSON API; render a publication-specific form or avoid presenting it as an answerable inbox item.

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 57d059c: a publication decision now renders the refused paths (escaped) or the blocked reason with a text reply. The answer route accepts that text from the form and resumes the session. Regression test: "asks how to continue when publishing needs a decision".

(Written by Claude Opus 5.5)

Comment thread cloudflare/src/inbox/page.tsx Outdated
Comment on lines +252 to +253
const reason = item.need === "start" || item.need === "retry" ? blocked(item, view) : null;
const job = item.need === "start" || item.need === "retry" ? work(item, view!) : 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.

P1 Badge Defer constructing retry actions until the item is actionable

When a failed pull-request job remains after its PR is closed or otherwise disappears from observed activity, blocked() correctly returns a refresh reason, but work(item, view!) is still evaluated first. For a pull fallback, work() dereferences item.activity!.data, which is null, so loading /inbox throws instead of showing the refresh action. Only call work() when reason is null.

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 57d059c: the retry/start action is built only when blocked() returns no reason, so a failed PR job whose pull request is gone shows the refresh instead of crashing. Regression test: "offers a refresh for a failed job whose pull request is no longer observed".

(Written by Claude Opus 5.5)

Comment on lines +211 to +214
const [reads, jobs] = await Promise.all([
env.DB.prepare("SELECT repository,item,seen,skipped FROM inbox_items WHERE user=?").bind(user).all<Read>(),
Promise.all(usable.map((view) => store.listJobs(env.DB, view.project.id))),
]);

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 Load active jobs beyond the 50-job history limit

For repositories with more than 50 jobs, store.listJobs() returns only the 50 newest records (ORDER BY createdAt DESC ... LIMIT 50), but the inbox treats that result as the complete job set. An older queued, running, or waiting job on another target can therefore disappear entirely, including any unanswered question. Load all active jobs separately or paginate history so active decisions are never truncated.

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 57d059c: the inbox uses store.listInboxJobs, which returns every queued, running, and waiting job along with the 50 newest, so an older open decision is never cut off. The JSON jobs listing keeps its 50-job page. Regression test: "keeps an older job's question beyond the newest fifty jobs".

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: f778d7d319

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…inbox

Codex review findings on #144:

- A publication decision from bot publication (#145) fell through to
  "could not be read" with no way to answer. The inbox now lists the
  refused paths or why the branch is blocked and takes a text reply,
  which the answer route accepts from the form.
- A failed PR job whose pull request is no longer observed crashed the
  inbox: the retry action dereferenced missing activity before the
  refresh reason was checked. The action is built only when unblocked.
- The inbox read only the 50 newest jobs, so an older waiting job
  vanished. It now also reads every queued, running, and waiting job.

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: 57d059cc74

ℹ️ 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 cloudflare/src/inbox/page.tsx Outdated
};
if (item.kind === "issue")
return { workflow: "plan", target: item.target, revision: commit, label: "Plan this issue" };
if (item.kind === "plan") return { workflow: "plan", revision: commit, label: "Start a new plan" };

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 the target when retrying planning interviews

When a targetless planning interview (plans/<uuid>) fails, this retry description omits target; resolve() therefore generates a new UUID and starts an unrelated planning job. The original failed item remains in the inbox, so clicking “Try again” duplicates the interview instead of retrying or clearing the failure.

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 0d464ab: a failed interview's retry posts its own plans/<id> target, and the jobs route accepts a UUID-shaped plan target, so the new job replaces the failure instead of starting a second interview. Regression test: "retries a failed planning interview on its own target".

(Written by Claude Opus 5.5)

Comment thread cloudflare/src/inbox/page.tsx Outdated
Comment on lines +259 to +260
if (item.kind !== "quest" && item.kind !== "plan" && item.kind !== "devshell" && !item.activity?.data)
return "Refresh to read this from GitHub.";

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 Do not offer retries for unavailable quests

When a failed quest job's target is no longer in state.available—for example, its branch was observed as claimed or the quest was removed—there is no entity for the target, but this check exempts every quest item and enables “Try again.” The submitted start request is then rejected by resolve() because the quest is not ready, so the inbox advertises an action that cannot succeed.

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 0d464ab: blocked() now refuses a quest that is not in state.available and says why, so a failed quest job no longer offers a retry the route would reject. It can still be skipped. Regression test: "offers no retry for a failed quest that is no longer ready".

(Written by Claude Opus 5.5)

Comment thread cloudflare/src/jobs/store.ts Outdated
export async function listInboxJobs(db: D1Database, repository: number) {
const rows = await db
.prepare(
"SELECT id FROM jobs WHERE repository=?1 AND (state IN ('queued','running','waiting') OR id IN (SELECT id FROM jobs WHERE repository=?1 ORDER BY createdAt DESC,id LIMIT 50)) ORDER BY createdAt DESC,id",

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 Include failed decisions beyond the history limit

The uncapped portion of this query includes active states but not failed, even though failed jobs are actionable inbox decisions. Once a repository has 50 newer jobs, an older unskipped failure is omitted; if its target still exists it is incorrectly presented as fresh work, and if not it disappears entirely, so the maintainer can no longer retry or skip that failure.

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 0d464ab: listInboxJobs also includes any failed job that is still its target's latest. Superseded failures stay out, so they can't hide a newer job. The 50-job test now also covers an older failed issue job, which stays "Failed" with "Try again".

(Written by Claude Opus 5.5)

Codex review findings on #144, round 2:

- Retrying a failed planning interview started an unrelated one under a
  new plans/<id>. The retry now keeps the target, which the jobs route
  accepts, so the new job replaces the failure.
- A failed quest job offered "Try again" after the quest stopped being
  ready, which the route then refused. The inbox explains instead.
- An older failure beyond the 50-job history showed as fresh work or
  vanished. The inbox also reads each target's latest failure.

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: 0d464abf6c

ℹ️ 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 cloudflare/src/jobs/store.ts Outdated
`SELECT id FROM jobs j WHERE repository=?1 AND (
state IN ('queued','running','waiting')
OR (state='failed' AND NOT EXISTS(SELECT 1 FROM jobs n WHERE n.repository=?1 AND n.target=j.target AND (n.createdAt>j.createdAt OR (n.createdAt=j.createdAt AND n.id<j.id))))
OR id IN (SELECT id FROM jobs WHERE repository=?1 ORDER BY createdAt DESC,id LIMIT 50)

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 Retain latest terminal jobs beyond the history window

When a target's latest job is completed or cancelled and 50 newer jobs exist on other targets, this query omits that terminal job. decisions() then sees only the still-open issue, PR, or ready quest and presents it as fresh work instead of setting it aside, allowing a maintainer to start duplicate work. Include every target's latest job regardless of state, while still limiting older history.

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 07c269c: listInboxJobs now returns each target's latest job however old, when it awaits a decision or its target is one of the observed issues, PRs, or ready quests. A new index on (repository, target, createdAt) backs the lookup, and the 50-job window is gone. The regression test now also covers a finished PR job that sits behind 50 newer jobs and stays set aside.

(Written by Claude Opus 5.5)

Comment thread cloudflare/src/inbox/model.ts Outdated

/** A quest's page on the project's board: its path without `.md`, or its epic's directory. */
export function questHref(project: Enrollment, path: string) {
return `/projects/github/${project.owner}/${project.name}/board/${path.replace(/(\/README)?\.md$/, "")}`;

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 Encode quest paths in inbox links

For valid quest filenames containing URL-reserved characters such as # or ?, this raw interpolation turns part of the filename into a fragment or query, so clicking the inbox item's title requests the wrong board route. The existing board URL builder encodes each path segment; apply the same encoding here before constructing the link.

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 07c269c: the inbox's questHref now uses the board's encodePath (now exported) and projectURL, so # and ? in a quest filename are encoded. Test: "encodes quest paths in board links".

(Written by Claude Opus 5.5)

Codex review findings on #144, round 3:

- A finished job beyond the 50-job window let its still-open issue, PR,
  or quest show as fresh work. The inbox now reads each target's latest
  job however old: every one awaiting a decision, plus any other whose
  target is still observed. An index on (repository, target, createdAt)
  backs the lookup, replacing the 50-job window.
- Quest links interpolated raw paths, so a `#` or `?` in a filename
  broke the board link. They now reuse the board's path encoding.

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: 07c269c7a2

ℹ️ 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 cloudflare/src/jobs/routes.ts Outdated
question.id,
user,
// An agent's answer must fit its prompt; the store checks an account choice against the owner's accounts.
question.kind === "agent" ? parseAnswer(parsePrompt(question.prompt), answer) : data(answer, 1024),

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 Enforce the publication limit on raw text

For publication questions, answer is a string from a textarea that permits 1,000 characters, but data(answer, 1024) limits the length of JSON.stringify(answer). Escaped characters count twice, so a valid reply containing, for example, 600 backslashes—or 1,000 characters with 23 newlines—is rejected as “Payload too large” even though it satisfies the form limit. Validate the raw publication text against the advertised limit or allow enough serialized overhead.

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. The route should check the raw text against the form's 1,000-character limit instead of the serialized length. This would be a fourth fix round, past the cap of three, so I'm leaving it for the maintainer to decide: one more small fix, or merge as is. For now such a reply gets a clear "Payload too large" refusal, and nothing is recorded wrongly.

(Written by Claude Opus 5.5)

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.

Fixed in 44f4c87, with the maintainer's approval for one more round. A publication reply is now checked as text against PUBLICATION_REPLY (1,000 characters). The form's maxlength uses the same constant. CRLF line breaks from forms are normalized first, so a line break counts as one character, as it does in the textarea. The regression test sends exactly 1,000 characters of quotes, backslashes, and CRLF line breaks, which pass, and 1,001, which are refused.

(Written by Claude Opus 5.5)

kixelated and others added 3 commits October 19, 2026 12:05
The inbox's reply box allows 1,000 characters, but the answer route
checked the JSON-serialized length against 1024, so quotes, backslashes,
and line breaks could get a valid reply refused as too large. The route
now checks the text itself against the same 1,000-character limit the
form uses, after normalizing the CRLF line breaks forms submit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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: 075f166126

ℹ️ 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 cloudflare/src/inbox/page.tsx Resolved
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated merged commit d177624 into main Oct 19, 2026
5 checks passed
@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary from the /quest-merge run:

(Written by Claude Opus 5.5)

@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: 6fe526c824

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

// Starting new work on a paused project would be refused, so it is not offered.
if (project.state !== "enabled") continue;
need = "start";
ended = !!job && !changed;

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 Let edited quests resurface after terminal jobs

When a completed or cancelled quest is subsequently edited on the default branch while remaining ready, this always marks it as ended because quest entities have at: 0, so changed can never exceed the job's updatedAt. Although the quest digest produces a new version, the unconditional ended flag keeps the item in the collapsed “Skipped and finished” section instead of making the changed quest an unread decision again.

Useful? React with 👍 / 👎.

return { workflow: "plan", target: item.target, revision: commit, label: "Plan this issue" };
// Only a failed interview is an item, so this retries it on the same target.
if (item.kind === "plan") return { workflow: "plan", target: item.target, revision: commit, label: "Try again" };
if (item.kind === "devshell") return { workflow: "bootstrap", revision: commit, label: "Add a devShell" };

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 Hide bootstrap retries once a flake exists

If a bootstrap job fails and flake.nix is later added to the default branch, this still offers “Try again” because the devshell path has no availability check. Submitting that form is deterministically rejected by routes.ts at the if (flake) throw new JobError("The default branch already has a flake") check, and refreshing cannot clear the stale failed item; treat the bootstrap as resolved or suppress its retry when a flake is now present.

Useful? React with 👍 / 👎.

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