Summary
When the first PR is close but not right, let the same feedback continue on the same branch / same PR instead of opening a duplicate. Today every widget submit is a brand-new sandbox, branch feedback/f2c-{id}, and PR.
“Iterate” here means follow-up instructions → more commits on the existing PR, not “merge everything into one mega-branch” (that idea is covered — and rejected — in the base-branch issue).
Current behavior
One submission = one isolated run:
| Step |
Implementation |
| Branch |
branchNameForFeedback(id) → feedback/f2c-{safeId} (lib/feedback-agent/e2b-feedback-pipeline-core.ts) |
| Clone |
Always from GitHub default branch (bootstrap-clone.sh + e2b-github.mjs default-branch) |
| PR |
finalize-feedback.sh pushes that unique head and opens a PR onto the default base |
| Status |
CODING → WAITING_FOR_REVIEW / FAILED / MERGED (WidgetFeedbackStatus) |
| GitHub webhook |
app/api/github/webhook/route.ts matches prUrl and sets MERGED or WAITING_FOR_REVIEW |
| Widget |
Local history in iframe localStorage only; no “add more context” on an in-flight item |
| Dashboard |
List + link to PR (components/repo/repo-feedbacks-panel.tsx); no retry / amend |
There is no way to send a second prompt that checks out the existing feedback/f2c-… branch and pushes another commit.
Problem
Client: “the button is still too small.” Owner today either:
- Asks them to submit again → second quota hit, second PR, two diffs to reconcile, or
- Fixes it by hand, which is the job we claimed to automate.
Duplicate PRs also fight if they touch the same files. Iteration on the open PR is how a human intern would work.
Proposed design
Product rule
Iterate = same WidgetFeedback row, same head branch, same PR. New agent run, additional commit(s). New widget submits that are unrelated still create new feedback rows (current behavior).
Do not dump follow-ups onto a shared feedback branch. Independent heads are what keep PRs reviewable.
Who can iterate
v1: the repo owner, from the dashboard (and later the widget if we can identify the same browser).
Reasons:
- Widget submitters are anonymous. Auto-attaching a stranger’s second message to a PR is a moderation / prompt-injection problem.
- Owners already review PRs; “Add instructions and re-run” next to
WAITING_FOR_REVIEW / FAILED is the natural control.
- Quota still decrements (another sandbox). Show remaining quota on the button.
v2 (optional): widget local history, if status is WAITING_FOR_REVIEW and localStorage has that id, show “Add a note for the developer”. Treat as untrusted extra context; owner still has to press Run, or auto-run only if we add a per-repo “allow visitor follow-ups” toggle (default off).
States
| Current status |
Iterate allowed? |
CODING |
No — queue or disable; one sandbox per feedback at a time |
WAITING_FOR_REVIEW |
Yes — checkout existing branch (not default base), apply new instructions, push |
FAILED |
Yes — retry with extra instructions or retry as-is |
MERGED |
No — tell them to submit new feedback (base has moved). Avoid rewriting history on main. |
If the GitHub PR is closed unmerged, treat like FAILED / allow reopen or new PR from same branch (GitHub allows reopen). Prefer: reopen if still the same head SHA relationship.
Pipeline changes
bootstrap-clone.sh today: git clone --depth 1 -b "$BASE" then checkout -b "$F2C_BRANCH".
For iterate:
- Clone default (or configured base) or fetch the existing head:
git clone --depth 1 -b "$F2C_BRANCH" if the remote branch exists.
- Prompt includes original feedback + follow-up thread (append-only list).
- Finalize: push to the same
F2C_BRANCH. If PR already exists, do not create-pr again; just push. e2b-github.mjs create-pr will 422 on duplicates — detect existing PR by prUrl on the row.
- Status: set back to
CODING while running, then WAITING_FOR_REVIEW.
- Email: either a distinct “agent updated the PR” template or skip (owner is the one who clicked). Do not spam
send-pr-created-email again.
Store follow-ups on the row, e.g. followUps Json as [{ body, createdAt, source: "owner" | "widget" }], and include them in buildOpencodeFeedbackPrompt.
Dashboard UX
On a feedback card with an open PR:
- Textarea: “Tell the agent what to change…”
- Button: Update PR (uses 1 quota)
- Disable while
CODING
- After click, existing auto-refresh (
RouteAutoRefresh) already polls
Widget (v1 stretch / v2)
Only if local history id matches a still-open item. Otherwise too easy to attach noise to the wrong PR.
Assumptions
- Follow-ups count against quota (
UserFeedbackLimitEvent). Each run costs an E2B VM + MiniMax tokens (agentLlmCostUsd already on the row — append or store last-run; do not overwrite history blindly; a small AgentRun child table is nicer if we are touching schema anyway, but v1 can overwrite last-run metrics and keep the follow-up text list).
- We never force-push to
main / the base branch.
- If the owner has merged locally and the remote PR is merged, GitHub webhook already sets
MERGED — iterate button disappears.
- Custom agent instructions on the repo still apply on every run.
Acceptance criteria
Out of scope
- Chat-with-the-agent inside the widget for anonymous visitors (v2).
- Reading GitHub review comments and auto-replying (nice, separate issue).
- Stacking unrelated tickets on one branch.
Implementation notes
lib/feedback-agent/run-e2b-feedback-agent.ts — add mode: "create" | "iterate", skip create-pr when prUrl set.
lib/feedback-agent/e2b/bootstrap-clone.sh — clone/fetch existing head when F2C_ITERATE=1.
lib/feedback-agent/e2b/e2b-github.mjs — optional find-pr by head ref.
app/api/e2b/webhook/route.ts — already keyed by feedback id; should keep working.
- New dashboard server action or
POST /api/feedback/[id]/iterate.
- Prompt builder: thread the conversation.
Risks
- Stale iterate: owner iterates after they already pushed extra commits by hand on that branch. Agent should
git pull the head first.
- Shallow clone (
--depth 1) may be insufficient to iterate if we need merge-base. For iterate, clone the head branch at depth 1 (the branch already contains the first commit) rather than rebasing from default each time. If default has moved, do not auto-rebase in v1 (conflict hell). Document that iterate is “continue this branch”, not “replay on latest main”.
- Prompt injection via visitor follow-ups if we enable widget v2 — keep owner-gated in v1.
See also
- Configurable PR base branch (orthogonal: where the PR targets; iteration still uses unique heads)
- Element picker (follow-up can keep the same selected node)
Summary
When the first PR is close but not right, let the same feedback continue on the same branch / same PR instead of opening a duplicate. Today every widget submit is a brand-new sandbox, branch
feedback/f2c-{id}, and PR.“Iterate” here means follow-up instructions → more commits on the existing PR, not “merge everything into one mega-branch” (that idea is covered — and rejected — in the base-branch issue).
Current behavior
One submission = one isolated run:
branchNameForFeedback(id)→feedback/f2c-{safeId}(lib/feedback-agent/e2b-feedback-pipeline-core.ts)bootstrap-clone.sh+e2b-github.mjs default-branch)finalize-feedback.shpushes that unique head and opens a PR onto the default baseCODING→WAITING_FOR_REVIEW/FAILED/MERGED(WidgetFeedbackStatus)app/api/github/webhook/route.tsmatchesprUrland setsMERGEDorWAITING_FOR_REVIEWlocalStorageonly; no “add more context” on an in-flight itemcomponents/repo/repo-feedbacks-panel.tsx); no retry / amendThere is no way to send a second prompt that checks out the existing
feedback/f2c-…branch and pushes another commit.Problem
Client: “the button is still too small.” Owner today either:
Duplicate PRs also fight if they touch the same files. Iteration on the open PR is how a human intern would work.
Proposed design
Product rule
Iterate = same
WidgetFeedbackrow, same head branch, same PR. New agent run, additional commit(s). New widget submits that are unrelated still create new feedback rows (current behavior).Do not dump follow-ups onto a shared
feedbackbranch. Independent heads are what keep PRs reviewable.Who can iterate
v1: the repo owner, from the dashboard (and later the widget if we can identify the same browser).
Reasons:
WAITING_FOR_REVIEW/FAILEDis the natural control.v2 (optional): widget local history, if status is
WAITING_FOR_REVIEWandlocalStoragehas thatid, show “Add a note for the developer”. Treat as untrusted extra context; owner still has to press Run, or auto-run only if we add a per-repo “allow visitor follow-ups” toggle (default off).States
CODINGWAITING_FOR_REVIEWFAILEDMERGEDmain.If the GitHub PR is closed unmerged, treat like
FAILED/ allow reopen or new PR from same branch (GitHub allows reopen). Prefer: reopen if still the same head SHA relationship.Pipeline changes
bootstrap-clone.shtoday:git clone --depth 1 -b "$BASE"thencheckout -b "$F2C_BRANCH".For iterate:
git clone --depth 1 -b "$F2C_BRANCH"if the remote branch exists.F2C_BRANCH. If PR already exists, do notcreate-pragain; just push.e2b-github.mjs create-prwill 422 on duplicates — detect existing PR byprUrlon the row.CODINGwhile running, thenWAITING_FOR_REVIEW.send-pr-created-emailagain.Store follow-ups on the row, e.g.
followUps Jsonas[{ body, createdAt, source: "owner" | "widget" }], and include them inbuildOpencodeFeedbackPrompt.Dashboard UX
On a feedback card with an open PR:
CODINGRouteAutoRefresh) already pollsWidget (v1 stretch / v2)
Only if local history
idmatches a still-open item. Otherwise too easy to attach noise to the wrong PR.Assumptions
UserFeedbackLimitEvent). Each run costs an E2B VM + MiniMax tokens (agentLlmCostUsdalready on the row — append or store last-run; do not overwrite history blindly; a smallAgentRunchild table is nicer if we are touching schema anyway, but v1 can overwrite last-run metrics and keep the follow-up text list).main/ the base branch.MERGED— iterate button disappears.Acceptance criteria
WAITING_FOR_REVIEWandFAILED, owner can submit follow-up text and trigger a new sandbox.feedback/f2c-{id}; existing PR updates; no second PR.CODINGthenWAITING_FOR_REVIEW(orFAILED).POST /f.CODINGis rejected.Out of scope
Implementation notes
lib/feedback-agent/run-e2b-feedback-agent.ts— addmode: "create" | "iterate", skip create-pr whenprUrlset.lib/feedback-agent/e2b/bootstrap-clone.sh— clone/fetch existing head whenF2C_ITERATE=1.lib/feedback-agent/e2b/e2b-github.mjs— optionalfind-prby head ref.app/api/e2b/webhook/route.ts— already keyed by feedback id; should keep working.POST /api/feedback/[id]/iterate.Risks
git pullthe head first.--depth 1) may be insufficient to iterate if we need merge-base. For iterate, clone the head branch at depth 1 (the branch already contains the first commit) rather than rebasing from default each time. If default has moved, do not auto-rebase in v1 (conflict hell). Document that iterate is “continue this branch”, not “replay on latest main”.See also