Skip to content

Commit c0b2dde

Browse files
authored
Add fix-with-agent capability to PR creation errors (#1460)
## TL;DR Added "Fix with Agent" functionality to PR creation error dialogs, allowing users to send error context to an active agent session for automated resolution. ## Problem When PR creation fails, users have no way to leverage the agent to diagnose and fix the issue. They can only copy the error message and manually investigate. ## Changes | error container with ✨ button | inline chip that holds context about the workflow / intended action | | --- | --- | | ![Screenshot 2026-04-02 at 9.57.04 AM.png](https://app.graphite.com/user-attachments/assets/ebae063a-7893-48f8-985a-7c78d2e7bc58.png)<br> | ![Screenshot 2026-04-02 at 10.02.07 AM.png](https://app.graphite.com/user-attachments/assets/01e791f3-29e3-4830-a339-0489227f66f6.png)<br> | - **New hook** **`useFixWithAgent`**: Manages sending structured error prompts to the active agent session, with validation that a connected session exists - **New utility** **`buildCreatePrFlowErrorPrompt`**: Generates contextual error prompts that explain the PR creation flow, the failed step, rollback behavior, and common error resolutions - **Updated** **`ErrorContainer`** **component**: Added optional "Fix with Agent" button (sparkle icon) alongside the existing copy error button - **Updated** **`CreatePrDialog`**: Integrated the hook and passes error context builder to enable the fix button when a session is available - **UX improvements**: After sending the error to the agent, the dialog closes and the agent's logs panel is activated for visibility ## How did you test this? Changes are integrated into the PR creation flow and will be tested through manual PR creation scenarios with active agent sessions.
1 parent 0520e9a commit c0b2dde

5 files changed

Lines changed: 150 additions & 15 deletions

File tree

apps/code/src/renderer/features/git-interaction/components/CreatePrDialog.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {
22
ErrorContainer,
33
GenerateButton,
44
} from "@features/git-interaction/components/GitInteractionDialogs";
5+
import { useFixWithAgent } from "@features/git-interaction/hooks/useFixWithAgent";
56
import { useGitInteractionStore } from "@features/git-interaction/state/gitInteractionStore";
67
import type { CreatePrStep } from "@features/git-interaction/types";
78
import type { DiffStats } from "@features/git-interaction/utils/diffStats";
9+
import { buildCreatePrFlowErrorPrompt } from "@features/git-interaction/utils/errorPrompts";
810
import {
911
CheckCircle,
1012
Circle,
@@ -133,6 +135,9 @@ export function CreatePrDialog({
133135
}: CreatePrDialogProps) {
134136
const store = useGitInteractionStore();
135137
const { actions } = store;
138+
const { canFixWithAgent, fixWithAgent } = useFixWithAgent(() =>
139+
buildCreatePrFlowErrorPrompt(store.createPrFailedStep),
140+
);
136141

137142
const { createPrStep: step } = store;
138143
const isExecuting = step !== "idle" && step !== "complete";
@@ -299,7 +304,17 @@ export function CreatePrDialog({
299304
/>
300305

301306
{step === "error" && store.createPrError && (
302-
<ErrorContainer error={store.createPrError} />
307+
<ErrorContainer
308+
error={store.createPrError}
309+
onFixWithAgent={
310+
canFixWithAgent
311+
? () => {
312+
fixWithAgent(store.createPrError ?? "");
313+
actions.closeCreatePr();
314+
}
315+
: undefined
316+
}
317+
/>
303318
)}
304319

305320
<Flex gap="2" justify="end">

apps/code/src/renderer/features/git-interaction/components/GitInteractionDialogs.tsx

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ import { useState } from "react";
2626

2727
const ICON_SIZE = 14;
2828

29-
export function ErrorContainer({ error }: { error: string }) {
29+
export function ErrorContainer({
30+
error,
31+
onFixWithAgent,
32+
}: {
33+
error: string;
34+
onFixWithAgent?: () => void;
35+
}) {
3036
const [copied, setCopied] = useState(false);
3137

3238
const handleCopy = async () => {
@@ -59,16 +65,30 @@ export function ErrorContainer({ error }: { error: string }) {
5965
>
6066
{error}
6167
</Text>
62-
<Tooltip content={copied ? "Copied!" : "Copy error"}>
63-
<IconButton
64-
size="1"
65-
variant="ghost"
66-
color="gray"
67-
onClick={handleCopy}
68-
>
69-
<Copy size={12} weight={copied ? "fill" : "regular"} />
70-
</IconButton>
71-
</Tooltip>
68+
<Flex gap="1" style={{ flexShrink: 0 }}>
69+
{onFixWithAgent && (
70+
<Tooltip content="Fix with Agent">
71+
<IconButton
72+
size="1"
73+
variant="ghost"
74+
color="gray"
75+
onClick={onFixWithAgent}
76+
>
77+
<Sparkle size={12} />
78+
</IconButton>
79+
</Tooltip>
80+
)}
81+
<Tooltip content={copied ? "Copied!" : "Copy error"}>
82+
<IconButton
83+
size="1"
84+
variant="ghost"
85+
color="gray"
86+
onClick={handleCopy}
87+
>
88+
<Copy size={12} weight={copied ? "fill" : "regular"} />
89+
</IconButton>
90+
</Tooltip>
91+
</Flex>
7292
</Flex>
7393
</Flex>
7494
</Box>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { DEFAULT_TAB_IDS } from "@features/panels/constants/panelConstants";
2+
import { usePanelLayoutStore } from "@features/panels/store/panelLayoutStore";
3+
import { findTabInTree } from "@features/panels/store/panelTree";
4+
import { getSessionService } from "@features/sessions/service/service";
5+
import { useSessionForTask } from "@features/sessions/stores/sessionStore";
6+
import { useNavigationStore } from "@stores/navigationStore";
7+
import { useCallback } from "react";
8+
import type { FixWithAgentPrompt } from "../utils/errorPrompts";
9+
10+
/**
11+
* Hook that sends a structured error prompt to the active agent session.
12+
* Derives taskId and session readiness from stores.
13+
*
14+
* `canFixWithAgent` is true when there's an active, connected session.
15+
*/
16+
export function useFixWithAgent(
17+
buildPrompt: (error: string) => FixWithAgentPrompt,
18+
): {
19+
canFixWithAgent: boolean;
20+
fixWithAgent: (error: string) => Promise<void>;
21+
} {
22+
const taskId = useNavigationStore((s) =>
23+
s.view.type === "task-detail" ? s.view.data?.id : undefined,
24+
);
25+
const session = useSessionForTask(taskId);
26+
const isSessionReady = session?.status === "connected";
27+
28+
const canFixWithAgent = !!(taskId && isSessionReady);
29+
30+
const fixWithAgent = useCallback(
31+
async (error: string) => {
32+
if (!taskId || !isSessionReady) return;
33+
34+
const { label, context } = buildPrompt(error);
35+
36+
const prompt = `<error_context label="${label}">${context}</error_context>\n\n\`\`\`\n${error}\n\`\`\``;
37+
getSessionService().sendPrompt(taskId, prompt);
38+
39+
const { taskLayouts, setActiveTab } = usePanelLayoutStore.getState();
40+
const layout = taskLayouts[taskId];
41+
if (layout) {
42+
const result = findTabInTree(layout.panelTree, DEFAULT_TAB_IDS.LOGS);
43+
if (result) {
44+
setActiveTab(taskId, result.panelId, DEFAULT_TAB_IDS.LOGS);
45+
}
46+
}
47+
},
48+
[buildPrompt, taskId, isSessionReady],
49+
);
50+
51+
return { canFixWithAgent, fixWithAgent };
52+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { CreatePrStep } from "../types";
2+
3+
export interface FixWithAgentPrompt {
4+
label: string;
5+
context: string;
6+
}
7+
8+
export function buildCreatePrFlowErrorPrompt(
9+
failedStep: CreatePrStep | null,
10+
): FixWithAgentPrompt {
11+
return {
12+
label: `Fix PR creation error`,
13+
context: `The user tried to create a pull request using the Create PR button in the UI, but it failed at the ${failedStep} step.
14+
15+
This flow is supposed to follow these steps:
16+
1. [creating-branch] Create a new feature branch, if needed (required if on default branch, optional otherwise)
17+
2. [committing] Commit changes
18+
3. [pushing] Push to remote
19+
4. [creating-pr] Create PR
20+
21+
When an error occurs, the app automatically performs a rollback. This means you are likely in the pre-error state, e.g. back on the user's original branch without any new commits.
22+
23+
Rollback scenarios:
24+
1. Branch creation fails -> check out user's original branch
25+
2. Commit fails -> use git reset to get back to the user's original state
26+
27+
Common errors and resolutions:
28+
- **Duplicate branch names** - guide the user towards using a different branch name, or sorting out any git issues that have led them to this state
29+
- **Commit hook failures** - this may be the result of missing dependencies, check failure (e.g. lints), or something else. Ensure you fully understand the issue before proceeding.
30+
31+
Your task is to help the user diagnose and fix the underlying issue (e.g. resolve merge conflicts, fix authentication, clean up git state).
32+
33+
IMPORTANT:
34+
- Do NOT attempt to complete the PR flow yourself (no commit, push, or gh pr create). The user will retry via the "Create PR" button once the issue is resolved.
35+
- You may fix the underlying issue, but always confirm destructive actions with the user first.
36+
- Start by diagnosing: run read-only commands to understand the current state before taking action.
37+
- When the issue is resolved, remind the user to retry by clicking the "Create PR" button in the UI.`,
38+
};
39+
}

apps/code/src/renderer/features/sessions/components/session-update/parseFileMentions.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import {
22
baseComponents,
33
defaultRemarkPlugins,
44
} from "@features/editor/components/MarkdownRenderer";
5-
import { File, GithubLogo } from "@phosphor-icons/react";
5+
import { File, GithubLogo, Warning } from "@phosphor-icons/react";
66
import { Code, Text } from "@radix-ui/themes";
77
import type { ReactNode } from "react";
88
import { memo } from "react";
99
import type { Components } from "react-markdown";
1010
import ReactMarkdown from "react-markdown";
1111

1212
const MENTION_TAG_REGEX =
13-
/<file\s+path="([^"]+)"\s*\/>|<github_issue\s+number="([^"]+)"(?:\s+title="([^"]*)")?(?:\s+url="([^"]*)")?\s*\/>/g;
14-
const MENTION_TAG_TEST = /<(?:file\s+path|github_issue\s+number)="[^"]+"/;
13+
/<file\s+path="([^"]+)"\s*\/>|<github_issue\s+number="([^"]+)"(?:\s+title="([^"]*)")?(?:\s+url="([^"]*)")?\s*\/>|<error_context\s+label="([^"]*)">[\s\S]*?<\/error_context>/g;
14+
const MENTION_TAG_TEST =
15+
/<(?:file\s+path|github_issue\s+number|error_context\s+label)="[^"]+"/;
1516

1617
const inlineComponents: Components = {
1718
...baseComponents,
@@ -113,6 +114,14 @@ export function parseMentionTags(content: string): ReactNode[] {
113114
onClick={issueUrl ? () => window.open(issueUrl, "_blank") : undefined}
114115
/>,
115116
);
117+
} else if (match[5]) {
118+
parts.push(
119+
<MentionChip
120+
key={`error-ctx-${matchIndex}`}
121+
icon={<Warning size={12} />}
122+
label={match[5]}
123+
/>,
124+
);
116125
}
117126

118127
lastIndex = matchIndex + match[0].length;

0 commit comments

Comments
 (0)