Skip to content

Commit d983b94

Browse files
fix: add doom loop detection (#3445)
Co-authored-by: Aiden Cline <[email protected]>
1 parent 14836de commit d983b94

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

packages/opencode/src/session/prompt.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export namespace SessionPrompt {
5656
const log = Log.create({ service: "session.prompt" })
5757
export const OUTPUT_TOKEN_MAX = 32_000
5858
const MAX_RETRIES = 10
59+
const DOOM_LOOP_THRESHOLD = 3
5960

6061
export const Event = {
6162
Idle: Bus.event(
@@ -1068,6 +1069,32 @@ export namespace SessionPrompt {
10681069
metadata: value.providerMetadata,
10691070
})
10701071
toolcalls[value.toolCallId] = part as MessageV2.ToolPart
1072+
1073+
const parts = await Session.getParts(assistantMsg.id)
1074+
const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD)
1075+
if (
1076+
lastThree.length === DOOM_LOOP_THRESHOLD &&
1077+
lastThree.every(
1078+
(p) =>
1079+
p.type === "tool" &&
1080+
p.tool === value.toolName &&
1081+
p.state.status !== "pending" &&
1082+
JSON.stringify(p.state.input) === JSON.stringify(value.input),
1083+
)
1084+
) {
1085+
await Permission.ask({
1086+
type: "doom-loop",
1087+
pattern: value.toolName,
1088+
sessionID: assistantMsg.sessionID,
1089+
messageID: assistantMsg.id,
1090+
callID: value.toolCallId,
1091+
title: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
1092+
metadata: {
1093+
tool: value.toolName,
1094+
input: value.input,
1095+
},
1096+
})
1097+
}
10711098
}
10721099
break
10731100
}

packages/tui/internal/components/chat/message.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,11 @@ func renderToolDetails(
504504
base := styles.NewStyle().Background(backgroundColor)
505505
text := base.Foreground(t.Text()).Bold(true).Render
506506
muted := base.Foreground(t.TextMuted()).Render
507-
permissionContent = "Permission required to run this tool:\n\n"
507+
if permission.Type == "doom-loop" {
508+
permissionContent = permission.Title + "\n\n"
509+
} else {
510+
permissionContent = "Permission required to run this tool:\n\n"
511+
}
508512
permissionContent += text(
509513
"enter ",
510514
) + muted(
@@ -642,9 +646,9 @@ func renderToolDetails(
642646
for _, item := range todos.([]any) {
643647
todo := item.(map[string]any)
644648
content := todo["content"]
645-
if content == nil {
646-
continue
647-
}
649+
if content == nil {
650+
continue
651+
}
648652
switch todo["status"] {
649653
case "completed":
650654
body += fmt.Sprintf("- [x] %s\n", content)

0 commit comments

Comments
 (0)