Skip to content

Commit 9d47f1e

Browse files
anandgupta42claude
andauthored
fix: [AI-187] feedback submission fails when repo labels don't exist (#188)
Add fallback retry without `--label` flag when `gh issue create` fails due to missing labels in the repository. The tool now: 1. Tries to create the issue with labels first 2. If that fails, retries without labels Also created the missing `from-cli`, `enhancement`, and `improvement` labels in the GitHub repository. Closes #187 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a879d9e commit 9d47f1e

File tree

2 files changed

+120
-19
lines changed

2 files changed

+120
-19
lines changed

packages/opencode/src/altimate/tools/feedback-submit.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ export const FeedbackSubmitTool = Tool.define("feedback_submit", {
6363
title: "Feedback submission failed",
6464
metadata: { error: "gh_auth_check_failed", issueUrl: "" },
6565
output:
66-
"Failed to verify `gh` authentication status. Please check your installation with:\n" +
67-
" `gh auth status`",
66+
"Failed to verify `gh` authentication status. Please check your installation with:\n" + " `gh auth status`",
6867
}
6968
}
7069
if (authStatus.exitCode !== 0) {
@@ -105,20 +104,44 @@ export const FeedbackSubmitTool = Tool.define("feedback_submit", {
105104
// Build labels
106105
const labels = ["user-feedback", "from-cli", CATEGORY_LABELS[args.category]]
107106

108-
// Create the issue
107+
// Create the issue — try with labels first, fall back without labels
108+
// if it fails (e.g. when labels don't exist in the repository yet)
109109
let issueResult: { stdout: Buffer; stderr: Buffer; exitCode: number }
110110
try {
111-
issueResult = await Bun.$`gh issue create --repo AltimateAI/altimate-code --title ${args.title} --body ${body} --label ${labels.join(",")}`.quiet().nothrow()
111+
issueResult =
112+
await Bun.$`gh issue create --repo AltimateAI/altimate-code --title ${args.title} --body ${body} --label ${labels.join(",")}`
113+
.quiet()
114+
.nothrow()
112115
} catch {
113116
return {
114117
title: "Feedback submission failed",
115118
metadata: { error: "issue_creation_failed", issueUrl: "" },
116-
output: "Failed to create GitHub issue. The `gh` CLI encountered an unexpected error.\n\nPlease check your gh CLI installation and try again.",
119+
output:
120+
"Failed to create GitHub issue. The `gh` CLI encountered an unexpected error.\n\nPlease check your gh CLI installation and try again.",
117121
}
118122
}
119123

120-
const stdout = issueResult.stdout.toString().trim()
121-
const stderr = issueResult.stderr.toString().trim()
124+
let stdout = issueResult.stdout.toString().trim()
125+
let stderr = issueResult.stderr.toString().trim()
126+
127+
// If creation failed (e.g. missing labels), retry without labels
128+
if (issueResult.exitCode !== 0 || !stdout || !stdout.includes("github.com")) {
129+
try {
130+
issueResult = await Bun.$`gh issue create --repo AltimateAI/altimate-code --title ${args.title} --body ${body}`
131+
.quiet()
132+
.nothrow()
133+
} catch {
134+
return {
135+
title: "Feedback submission failed",
136+
metadata: { error: "issue_creation_failed", issueUrl: "" },
137+
output:
138+
"Failed to create GitHub issue. The `gh` CLI encountered an unexpected error.\n\nPlease check your gh CLI installation and try again.",
139+
}
140+
}
141+
142+
stdout = issueResult.stdout.toString().trim()
143+
stderr = issueResult.stderr.toString().trim()
144+
}
122145

123146
if (issueResult.exitCode !== 0 || !stdout || !stdout.includes("github.com")) {
124147
const errorDetail = stderr || stdout || "No output from gh CLI"

packages/opencode/test/tool/feedback-submit.test.ts

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -411,23 +411,21 @@ describe("tool.feedback_submit", () => {
411411

412412
expect(result.title).toBe("Feedback submitted")
413413
expect(result.metadata.error).toBe("")
414-
expect(result.metadata.issueUrl).toBe(
415-
"https://github.com/AltimateAI/altimate-code/issues/42",
416-
)
414+
expect(result.metadata.issueUrl).toBe("https://github.com/AltimateAI/altimate-code/issues/42")
417415
expect(result.output).toContain("successfully")
418-
expect(result.output).toContain(
419-
"https://github.com/AltimateAI/altimate-code/issues/42",
420-
)
416+
expect(result.output).toContain("https://github.com/AltimateAI/altimate-code/issues/42")
421417
})
422418

423-
test("returns failure when issue creation output has no github URL", async () => {
419+
test("returns failure when both label and no-label attempts have no github URL", async () => {
424420
const tool = await FeedbackSubmitTool.init()
425421

426422
// gh --version
427423
pushShellResult("gh version 2.40.0")
428424
// gh auth status
429425
pushShellResult("Logged in as user", 0)
430-
// gh issue create fails with unexpected output
426+
// gh issue create with labels — fails
427+
pushShellResult("some error occurred")
428+
// gh issue create without labels — also fails
431429
pushShellResult("some error occurred")
432430

433431
const result = await tool.execute(
@@ -445,11 +443,14 @@ describe("tool.feedback_submit", () => {
445443
expect(result.output).toContain("Failed to create GitHub issue")
446444
})
447445

448-
test("returns failure when issue creation returns empty output", async () => {
446+
test("returns failure when both attempts return empty output", async () => {
449447
const tool = await FeedbackSubmitTool.init()
450448

451449
pushShellResult("gh version 2.40.0")
452450
pushShellResult("Logged in", 0)
451+
// with labels — empty
452+
pushShellResult("")
453+
// without labels — also empty
453454
pushShellResult("")
454455

455456
const result = await tool.execute(
@@ -466,13 +467,40 @@ describe("tool.feedback_submit", () => {
466467
expect(result.metadata.error).toBe("issue_creation_failed")
467468
})
468469

469-
test("returns failure when issue creation has non-zero exitCode even with stdout", async () => {
470+
test("succeeds via fallback when label creation fails but no-label attempt works", async () => {
471+
const tool = await FeedbackSubmitTool.init()
472+
473+
pushShellResult("gh version 2.40.0")
474+
pushShellResult("Logged in", 0)
475+
// gh issue create with labels — fails (e.g. missing label)
476+
shellResults.push({ text: "label 'from-cli' not found", exitCode: 1 })
477+
// gh issue create without labels — succeeds
478+
pushShellResult("https://github.com/AltimateAI/altimate-code/issues/99")
479+
480+
const result = await tool.execute(
481+
{
482+
title: "Test fallback",
483+
category: "feature" as const,
484+
description: "test",
485+
include_context: false,
486+
},
487+
ctx,
488+
)
489+
490+
expect(result.title).toBe("Feedback submitted")
491+
expect(result.metadata.error).toBe("")
492+
expect(result.metadata.issueUrl).toBe("https://github.com/AltimateAI/altimate-code/issues/99")
493+
})
494+
495+
test("returns failure when label attempt has non-zero exitCode and retry also fails", async () => {
470496
const tool = await FeedbackSubmitTool.init()
471497

472498
pushShellResult("gh version 2.40.0")
473499
pushShellResult("Logged in", 0)
474-
// gh issue create exits non-zero (e.g. label doesn't exist) with partial output
500+
// gh issue create with labels — exits non-zero
475501
shellResults.push({ text: "https://github.com/AltimateAI/altimate-code/issues/1", exitCode: 1 })
502+
// retry without labels — also fails
503+
shellResults.push({ text: "rate limit exceeded", exitCode: 1 })
476504

477505
const result = await tool.execute(
478506
{
@@ -509,6 +537,30 @@ describe("tool.feedback_submit", () => {
509537
expect(result.title).toBe("Feedback submission failed")
510538
expect(result.metadata.error).toBe("issue_creation_failed")
511539
})
540+
541+
test("returns failure when retry without labels throws", async () => {
542+
const tool = await FeedbackSubmitTool.init()
543+
544+
pushShellResult("gh version 2.40.0")
545+
pushShellResult("Logged in", 0)
546+
// first attempt fails (non-zero exit)
547+
shellResults.push({ text: "label not found", exitCode: 1 })
548+
// retry throws
549+
pushShellThrow()
550+
551+
const result = await tool.execute(
552+
{
553+
title: "Test",
554+
category: "bug" as const,
555+
description: "test",
556+
include_context: false,
557+
},
558+
ctx,
559+
)
560+
561+
expect(result.title).toBe("Feedback submission failed")
562+
expect(result.metadata.error).toBe("issue_creation_failed")
563+
})
512564
})
513565

514566
// -------------------------------------------------------------------------
@@ -687,7 +739,7 @@ describe("tool.feedback_submit", () => {
687739
expect(createCall).toContain("My specific title")
688740
})
689741

690-
test("makes exactly 3 shell calls for successful submission", async () => {
742+
test("makes exactly 3 shell calls for successful submission when labels exist", async () => {
691743
const tool = await FeedbackSubmitTool.init()
692744

693745
pushShellResult("gh version 2.40.0")
@@ -707,6 +759,32 @@ describe("tool.feedback_submit", () => {
707759
expect(shellCalls.length).toBe(3)
708760
})
709761

762+
test("makes exactly 4 shell calls when label attempt fails and retry succeeds", async () => {
763+
const tool = await FeedbackSubmitTool.init()
764+
765+
pushShellResult("gh version 2.40.0")
766+
pushShellResult("Logged in", 0)
767+
// with labels — fails
768+
shellResults.push({ text: "label not found", exitCode: 1 })
769+
// without labels — succeeds
770+
pushShellResult("https://github.com/AltimateAI/altimate-code/issues/1")
771+
772+
await tool.execute(
773+
{
774+
title: "Test",
775+
category: "bug" as const,
776+
description: "test",
777+
include_context: false,
778+
},
779+
ctx,
780+
)
781+
782+
expect(shellCalls.length).toBe(4)
783+
// Verify the retry call does NOT contain --label
784+
const retryCall = shellCalls[3].join("")
785+
expect(retryCall).not.toContain("--label")
786+
})
787+
710788
test("makes only 1 shell call when gh is not installed", async () => {
711789
const tool = await FeedbackSubmitTool.init()
712790

0 commit comments

Comments
 (0)