Skip to content

feat(drafts): Draft & Releases mode behind a per-agent flag - #6851

Open
guitavano wants to merge 2 commits into
mainfrom
guitavano/simplify-branches-drafts
Open

feat(drafts): Draft & Releases mode behind a per-agent flag#6851
guitavano wants to merge 2 commits into
mainfrom
guitavano/simplify-branches-drafts

Conversation

@guitavano

@guitavano guitavano commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in Draft & Releases mode for code agents — a per-agent flag (metadata.draftsMode, default off) that replaces the classic branch/PR picker with a named-drafts UX. When off, everything behaves exactly as before.

When enabled:

  • Releases switcher in place of the branch/PR picker: pinned Produção (the base branch), a curated list of named, color-coded drafts, and Novo Rascunho (creates an auto-numbered "Rascunho N" instantly).
  • Per-draft ⋯ menu: rename (inline) and discard (removes the entry and deletes the git branch).
  • Advanced: adopt a specific existing branch or open PR as a named draft.
  • Read-only production: on Produção the CMS stays fully navigable (open sections, drill into nested fields, scroll) but every value widget is inert — dimmed, cursor-not-allowed, with a hover tooltip "Produção é somente leitura. Comece um novo rascunho para editar." Structural mutations (add/delete/reorder sections, array items, page name/path) are disabled too.
  • The chat composer on Produção shows a "start a new draft to edit" CTA.
  • Publish sends you to Produção and discards the merged draft (CMS + coding session).
  • Optimistic switcher writes — create/rename/discard feel instant.
  • Settings toggle ("Modo Rascunhos & Versões") to enable it per code agent.

Backend: new GITHUB_DELETE_BRANCH tool (connection-scoped, refuses to delete the repo's default/production branch, idempotent); releases + draftsMode added to the VirtualMCP metadata schema; tool contracts regenerated.

Safety — off by default

With the flag off, the switcher routes to the restored classic branch/PR picker (branch-picker-legacy.tsx, faithful to main), read-only/publish behaviors fall back to classic, and no displayed string changes. The new i18n keys and the GITHUB_DELETE_BRANCH tool / metadata fields are dormant until the flag is on. The only non-gated addition is the settings toggle itself (shown, off).

Also included

  • Raises the dev embedded-postgres max_connections to 500 (ensure-services.ts) to avoid a boot-time "too many clients" retry storm. Dev-only.

Testing

  • bun run check (web/api/shared tsc), bun run knip, bun run fmt, bun run lint — clean.
  • Unit tests pass, including a new routing test (chat-mode-row.test.tsx) asserting draftsMode selects the releases switcher vs the classic picker.

Follow-ups

  • Gate the union/multivariate branch-type selectors in read-only (rarer widgets).
  • Optional: align the "Produção" base-branch source with the Fast Preview publish base (PR base vs git-status base).

🤖 Generated with Claude Code


Summary by cubic

Adds an opt-in Draft & Releases mode for code agents, gated by a per-agent metadata.draftsMode flag that defaults to off. When off, the classic branch/PR picker and post-publish behavior stay exactly as they are.

When enabled

  • Replaces the branch/PR picker with a releases switcher: pinned Produção, named color-coded drafts, and one-click "Novo Rascunho".
  • Per-draft menu supports inline rename and discard; discarding also deletes the git branch.
  • Advanced mode can adopt an existing branch or open PR as a named draft.
  • Production is read-only: the CMS stays navigable, but value widgets and structural mutations are disabled with a "start a new draft to edit" tooltip.
  • Publishing sends you to production and discards the merged draft.
  • Switcher create/rename/discard writes are optimistic.
  • Adds a settings toggle per agent and a new GITHUB_DELETE_BRANCH tool that refuses to delete the default branch.

Rollout

  • With the flag off, no displayed string changes; the new tool, metadata fields, and i18n keys stay dormant.
  • The only non-gated addition is the settings toggle, shown off.
  • Dev-only: raises embedded-postgres max_connections to 500 to avoid a boot-time retry storm.

Written for commit b9dc277. Summary will update on new commits.

Review in cubic

guitavano and others added 2 commits September 2, 2026 03:38
Opt-in "Draft & Releases mode" (metadata.draftsMode, default off) that
replaces the classic branch/PR picker with a named-drafts UX:

- Releases switcher: create ("Novo Rascunho", auto-numbered), rename,
  discard, and "Advanced" (adopt a specific branch or open PR as a draft).
- Read-only production: the CMS stays navigable but every value widget is
  inert with a "start a new draft to edit" tooltip; mutations disabled.
- Publish → production + discard the merged draft (CMS and coding session).
- Optimistic switcher writes (create/rename/delete feel instant).
- New GITHUB_DELETE_BRANCH tool (refuses to delete the default branch).
- Settings toggle to enable it per code agent.

Off by default: agents keep the classic branch/PR picker and post-publish
behavior — no visible change unless the flag is enabled.

Also raises the dev embedded-postgres max_connections to 500 to avoid a
boot-time "too many clients" retry storm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…anches-drafts

# Conflicts:
#	packages/shared/src/sdk/types/virtual-mcp.ts
#	packages/shared/src/tools/tool-io.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

20 issues found and verified against the latest diff

You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/web/src/components/sections-editor/fields/read-only-pane.tsx">

<violation number="1" location="apps/web/src/components/sections-editor/fields/read-only-pane.tsx:45">
P2: In read-only production, `inert` removes the entire field subtree from the accessibility tree, so screen-reader users cannot read values or labels, and the tooltip cannot be reached from the keyboard. Keep the value content exposed while disabling mutation controls through their read-only/disabled semantics, and provide a focusable wrapper if the explanation must be available to keyboard users.</violation>
</file>

<file name="apps/web/src/components/thread/github/start-draft-cta.tsx">

<violation number="1" location="apps/web/src/components/thread/github/start-draft-cta.tsx:26">
P2: Rapidly clicking this CTA can create multiple drafts before the first mutation finishes, and stale `releases` snapshots can overwrite one another. Disable the CTA or guard the creation handler until `createDraft()` settles.</violation>
</file>

<file name="apps/web/src/components/sandbox/blocks/blocks-panel.tsx">

<violation number="1" location="apps/web/src/components/sandbox/blocks/blocks-panel.tsx:109">
P1: When drafts mode shows a production loader, the JSON editor still edits and autosaves the production branch because `RunnableBlockEditor` does not consume this read-only context. Pass read-only into that editor and disable its JSON editing/save path as well.</violation>
</file>

<file name="apps/web/src/components/thread/github/use-version-gate.ts">

<violation number="1" location="apps/web/src/components/thread/github/use-version-gate.ts:75">
P2: When a user creates drafts from the production CTA, every draft is named `Draft`/`Rascunho`, so the Releases switcher cannot distinguish them. Generate the next numbered draft name here, matching the branch picker's `nextDraftName` behavior.</violation>
</file>

<file name="apps/web/src/components/sections-editor/section-list.tsx">

<violation number="1" location="apps/web/src/components/sections-editor/section-list.tsx:640">
P1: In read-only mode, when the section list is empty the empty-state Add section button remains enabled (`disabled={!canAddSection}`) and calls onAddSection, breaking the read-only guarantee. Apply the same `readOnly ||` gate there that was added to the main Add section button.</violation>
</file>

<file name="apps/web/src/components/sections-editor/schema-form.tsx">

<violation number="1" location="apps/web/src/components/sections-editor/schema-form.tsx:163">
P1: Production primitive-array items and direct `renderField` callers remain editable because `ro` checks `props.readOnly`, but those callers rely on `ReadOnlyContext` and omit the prop. Pass the context state into every direct `renderField` call, including `ArrayField` and `GeneralSeoForm`, or make the renderer consume the context.</violation>
</file>

<file name="packages/shared/src/tools/registry-metadata.ts">

<violation number="1" location="packages/shared/src/tools/registry-metadata.ts:248">
P1: When a regular member discards a draft, `GITHUB_DELETE_BRANCH` fails authorization because this addition does not grant it through basic usage or any selectable capability. Add its `MANAGEMENT_TOOLS` metadata, mark the destructive action appropriately, and include it in the intended permission capability so discovery and draft discard work.</violation>
</file>

<file name="apps/api/src/tools/github/delete-branch.ts">

<violation number="1" location="apps/api/src/tools/github/delete-branch.ts:94">
P1: When GitHub rejects deletion with 422, this condition reports the branch as already deleted. Preserve 422 failures, or accept them only when the response explicitly says the reference is missing, so failed discards do not silently leave branches behind.</violation>
</file>

<file name="apps/web/src/components/thread/github/cms-header-actions.tsx">

<violation number="1" location="apps/web/src/components/thread/github/cms-header-actions.tsx:246">
P2: When the thread branch update fails during draft-mode publish completion, this `await` resolves immediately and the next line deletes the merged branch anyway. Return and await the underlying `setBranch` operation before deleting the draft, so a failed switch cannot leave the thread pointing at a deleted branch.</violation>
</file>

<file name="apps/web/src/components/thread/github/branch-picker.tsx">

<violation number="1" location="apps/web/src/components/thread/github/branch-picker.tsx:160">
P2: When release metadata persistence fails, this ignored promise still switches to the newly minted branch. Catch the failure and reconcile the branch/release state so the user does not get an unlisted, undiscardable branch.</violation>

<violation number="2" location="apps/web/src/components/thread/github/branch-picker.tsx:181">
P2: When a rename fails, `saveRename` immediately closes the editor, so the entered name disappears and the user cannot retry it. Keep the editor open until `renameRelease` succeeds.

(Based on your team's feedback about keeping edits open on save failure.)</violation>

<violation number="3" location="apps/web/src/components/thread/github/branch-picker.tsx:473">
P2: When the Advanced branch request fails, this view reports an empty repository instead of the GitHub error. Render `isError` separately with the existing load-failure message and a retry path.</violation>

<violation number="4" location="apps/web/src/components/thread/github/branch-picker.tsx:550">
P2: When Advanced lists the repository base branch, this handler adopts it as a release and creates a draft entry for read-only production. Pass `baseBranch` into `AdvancedPicker` and exclude it before calling `onAdopt`.</violation>
</file>

<file name="apps/web/src/components/chat/input.tsx">

<violation number="1" location="apps/web/src/components/chat/input.tsx:643">
P2: When a web thread is pinned to an unsupported coding harness, this production branch returns the draft CTA before `hostedRuntimeBlocked` is checked. Check the hosted-runtime guard before offering the CTA so coding-agent threads cannot bypass the desktop-only restriction.</violation>
</file>

<file name="apps/web/src/components/sandbox/content/content-browser.tsx">

<violation number="1" location="apps/web/src/components/sandbox/content/content-browser.tsx:1186">
P1: When production is selected, the sidebar and dialogs remain outside this provider, so create, rename, delete, duplicate, and JSON-save actions can still mutate production. Gate `ItemList` and every mutation dialog/handler, and make right-pane structural controls honor `useIsReadOnly` before enabling this mode.</violation>
</file>

<file name="apps/web/src/components/thread/github/branch-picker-legacy.tsx">

<violation number="1" location="apps/web/src/components/thread/github/branch-picker-legacy.tsx:303">
P2: When all returned pull requests come from forks, the picker shows “No open pull requests” even though it found fork PRs and reports them below. Suppress the empty state when `hiddenForkPrs > 0` so the message remains accurate.</violation>

<violation number="2" location="apps/web/src/components/thread/github/branch-picker-legacy.tsx:324">
P2: When a pull request title contains an out-of-range numeric HTML entity, `decodeHtmlEntities(pr.title)` throws and can break the picker render. Validate the code point range or make the decoder leave invalid entities unchanged before rendering GitHub titles.</violation>
</file>

<file name="apps/web/src/components/thread/github/header-actions.tsx">

<violation number="1" location="apps/web/src/components/thread/github/header-actions.tsx:340">
P2: When the task branch is temporarily null or differs from the live sandbox branch, publishing merges `githubHeadBranch` but cleanup captures `branch`. Use the actual merged branch for `deleteRelease` so the merged draft is discarded and an unrelated branch is not removed.</violation>
</file>

<file name="apps/web/src/components/thread/github/use-releases.ts">

<violation number="1" location="apps/web/src/components/thread/github/use-releases.ts:85">
P2: When two release writes overlap, each mutation sends a full array from a stale `releases` snapshot, so the last server update can silently discard the other change. Serialize release writes or compute each update from the latest cache/server state before persisting.</violation>

<violation number="2" location="apps/web/src/components/thread/github/use-releases.ts:93">
P2: When branch deletion fails, `deleteRelease` has already removed the release from metadata, so discard leaves an orphaned GitHub branch and loses the UI entry needed to retry. Roll back the metadata write when `GITHUB_DELETE_BRANCH` fails, and surface the original failure.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

// On production the editor stays visible but read-only per widget.
const gateReadOnly = (children: ReactNode) => (
<div data-testid="blocks-panel" className="h-full min-h-0 overflow-hidden">
<ReadOnlyPane readOnly={readOnly} className="h-full min-h-0">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: When drafts mode shows a production loader, the JSON editor still edits and autosaves the production branch because RunnableBlockEditor does not consume this read-only context. Pass read-only into that editor and disable its JSON editing/save path as well.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/sandbox/blocks/blocks-panel.tsx, line 109:

<comment>When drafts mode shows a production loader, the JSON editor still edits and autosaves the production branch because `RunnableBlockEditor` does not consume this read-only context. Pass read-only into that editor and disable its JSON editing/save path as well.</comment>

<file context>
@@ -95,28 +103,32 @@ export function BlocksPanel({
+  // On production the editor stays visible but read-only per widget.
+  const gateReadOnly = (children: ReactNode) => (
+    <div data-testid="blocks-panel" className="h-full min-h-0 overflow-hidden">
+      <ReadOnlyPane readOnly={readOnly} className="h-full min-h-0">
+        {children}
+      </ReadOnlyPane>
</file context>

size="sm"
className="mt-2 w-full"
disabled={!canAddSection}
disabled={readOnly || !canAddSection}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: In read-only mode, when the section list is empty the empty-state Add section button remains enabled (disabled={!canAddSection}) and calls onAddSection, breaking the read-only guarantee. Apply the same readOnly || gate there that was added to the main Add section button.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/sections-editor/section-list.tsx, line 640:

<comment>In read-only mode, when the section list is empty the empty-state Add section button remains enabled (`disabled={!canAddSection}`) and calls onAddSection, breaking the read-only guarantee. Apply the same `readOnly ||` gate there that was added to the main Add section button.</comment>

<file context>
@@ -632,7 +637,7 @@ export function SectionList({
         size="sm"
         className="mt-2 w-full"
-        disabled={!canAddSection}
+        disabled={readOnly || !canAddSection}
         onClick={onAddSection}
       >
</file context>


// Read-only: leaf value widgets get a read-only wrap; containers stay navigable.
const ro = (node: ReactNode): ReactNode =>
props.readOnly ? (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: Production primitive-array items and direct renderField callers remain editable because ro checks props.readOnly, but those callers rely on ReadOnlyContext and omit the prop. Pass the context state into every direct renderField call, including ArrayField and GeneralSeoForm, or make the renderer consume the context.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/sections-editor/schema-form.tsx, line 163:

<comment>Production primitive-array items and direct `renderField` callers remain editable because `ro` checks `props.readOnly`, but those callers rely on `ReadOnlyContext` and omit the prop. Pass the context state into every direct `renderField` call, including `ArrayField` and `GeneralSeoForm`, or make the renderer consume the context.</comment>

<file context>
@@ -156,6 +158,14 @@ function renderResolvedBlockRefValue(props: FieldProps): ReactNode | null {
 
+  // Read-only: leaf value widgets get a read-only wrap; containers stay navigable.
+  const ro = (node: ReactNode): ReactNode =>
+    props.readOnly ? (
+      <ReadOnlyFieldWrap key={props.path}>{node}</ReadOnlyFieldWrap>
+    ) : (
</file context>

"GITHUB_SEARCH_BRANCHES",
"GITHUB_PR_STATE",
"GITHUB_LAST_PUBLISHED_PR",
"GITHUB_DELETE_BRANCH",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: When a regular member discards a draft, GITHUB_DELETE_BRANCH fails authorization because this addition does not grant it through basic usage or any selectable capability. Add its MANAGEMENT_TOOLS metadata, mark the destructive action appropriately, and include it in the intended permission capability so discovery and draft discard work.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/shared/src/tools/registry-metadata.ts, line 248:

<comment>When a regular member discards a draft, `GITHUB_DELETE_BRANCH` fails authorization because this addition does not grant it through basic usage or any selectable capability. Add its `MANAGEMENT_TOOLS` metadata, mark the destructive action appropriately, and include it in the intended permission capability so discovery and draft discard work.</comment>

<file context>
@@ -245,6 +245,7 @@ const ALL_TOOL_NAMES = [
   "GITHUB_SEARCH_BRANCHES",
   "GITHUB_PR_STATE",
   "GITHUB_LAST_PUBLISHED_PR",
+  "GITHUB_DELETE_BRANCH",
 
   // Search tools
</file context>

signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),
},
);
// 204 = deleted; 422/404 = ref already gone — idempotent success.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: When GitHub rejects deletion with 422, this condition reports the branch as already deleted. Preserve 422 failures, or accept them only when the response explicitly says the reference is missing, so failed discards do not silently leave branches behind.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/tools/github/delete-branch.ts, line 94:

<comment>When GitHub rejects deletion with 422, this condition reports the branch as already deleted. Preserve 422 failures, or accept them only when the response explicitly says the reference is missing, so failed discards do not silently leave branches behind.</comment>

<file context>
@@ -0,0 +1,108 @@
+        signal: AbortSignal.timeout(GITHUB_TIMEOUT_MS),
+      },
+    );
+    // 204 = deleted; 422/404 = ref already gone — idempotent success.
+    if (
+      delRes.status !== 204 &&
</file context>

{t("thread.branchPicker.loadingPullRequests")}
</div>
)}
{!prsError && !prsLoading && (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When all returned pull requests come from forks, the picker shows “No open pull requests” even though it found fork PRs and reports them below. Suppress the empty state when hiddenForkPrs > 0 so the message remains accurate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/thread/github/branch-picker-legacy.tsx, line 303:

<comment>When all returned pull requests come from forks, the picker shows “No open pull requests” even though it found fork PRs and reports them below. Suppress the empty state when `hiddenForkPrs > 0` so the message remains accurate.</comment>

<file context>
@@ -0,0 +1,506 @@
+                    {t("thread.branchPicker.loadingPullRequests")}
+                  </div>
+                )}
+                {!prsError && !prsLoading && (
+                  <CommandEmpty>
+                    {search.trim()
</file context>
Suggested change
{!prsError && !prsLoading && (
{!prsError && !prsLoading && hiddenForkPrs === 0 && (

<GitPullRequest className="mr-2 h-4 w-4 shrink-0 text-muted-foreground" />
<div className="flex min-w-0 flex-1 flex-col">
<span className="truncate">
{decodeHtmlEntities(pr.title)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When a pull request title contains an out-of-range numeric HTML entity, decodeHtmlEntities(pr.title) throws and can break the picker render. Validate the code point range or make the decoder leave invalid entities unchanged before rendering GitHub titles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/thread/github/branch-picker-legacy.tsx, line 324:

<comment>When a pull request title contains an out-of-range numeric HTML entity, `decodeHtmlEntities(pr.title)` throws and can break the picker render. Validate the code point range or make the decoder leave invalid entities unchanged before rendering GitHub titles.</comment>

<file context>
@@ -0,0 +1,506 @@
+                        <GitPullRequest className="mr-2 h-4 w-4 shrink-0 text-muted-foreground" />
+                        <div className="flex min-w-0 flex-1 flex-col">
+                          <span className="truncate">
+                            {decodeHtmlEntities(pr.title)}
+                          </span>
+                          <span className="truncate text-xs text-muted-foreground">
</file context>


const switchToFreshBranch = async () => {
if (draftsModeEnabled(vm)) {
const published = branch;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When the task branch is temporarily null or differs from the live sandbox branch, publishing merges githubHeadBranch but cleanup captures branch. Use the actual merged branch for deleteRelease so the merged draft is discarded and an unrelated branch is not removed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/thread/github/header-actions.tsx, line 340:

<comment>When the task branch is temporarily null or differs from the live sandbox branch, publishing merges `githubHeadBranch` but cleanup captures `branch`. Use the actual merged branch for `deleteRelease` so the merged draft is discarded and an unrelated branch is not removed.</comment>

<file context>
@@ -333,6 +336,14 @@ export function HeaderActions({ virtualMcpId }: Props) {
 
   const switchToFreshBranch = async () => {
+    if (draftsModeEnabled(vm)) {
+      const published = branch;
+      await setCurrentTaskBranch(baseBranch);
+      if (published && published !== baseBranch) {
</file context>
Suggested change
const published = branch;
const published = githubHeadBranch;

});
};

const createRelease = (release: Release) => write([...releases, release]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When two release writes overlap, each mutation sends a full array from a stale releases snapshot, so the last server update can silently discard the other change. Serialize release writes or compute each update from the latest cache/server state before persisting.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/thread/github/use-releases.ts, line 85:

<comment>When two release writes overlap, each mutation sends a full array from a stale `releases` snapshot, so the last server update can silently discard the other change. Serialize release writes or compute each update from the latest cache/server state before persisting.</comment>

<file context>
@@ -0,0 +1,103 @@
+      });
+  };
+
+  const createRelease = (release: Release) => write([...releases, release]);
+
+  const renameRelease = (branch: string, name: string) =>
</file context>

const deleteRelease = async (branch: string) => {
await write(releases.filter((r) => r.branch !== branch));
if (repo?.connectionId) {
await callStudioTool(org.slug, "GITHUB_DELETE_BRANCH", {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When branch deletion fails, deleteRelease has already removed the release from metadata, so discard leaves an orphaned GitHub branch and loses the UI entry needed to retry. Roll back the metadata write when GITHUB_DELETE_BRANCH fails, and surface the original failure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/src/components/thread/github/use-releases.ts, line 93:

<comment>When branch deletion fails, `deleteRelease` has already removed the release from metadata, so discard leaves an orphaned GitHub branch and loses the UI entry needed to retry. Roll back the metadata write when `GITHUB_DELETE_BRANCH` fails, and surface the original failure.</comment>

<file context>
@@ -0,0 +1,103 @@
+  const deleteRelease = async (branch: string) => {
+    await write(releases.filter((r) => r.branch !== branch));
+    if (repo?.connectionId) {
+      await callStudioTool(org.slug, "GITHUB_DELETE_BRANCH", {
+        connectionId: repo.connectionId,
+        owner: repo.owner,
</file context>

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