summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorElecTwix <[email protected]>2025-10-30 08:13:18 +0300
committerGitHub <[email protected]>2025-10-30 00:13:18 -0500
commitd983b9485d91306da6ca72d726348812840f2831 (patch)
tree333133b11da67821b02f35e6b9ebb713946c7a73
parent14836de27662813562b9ced3285f0ef5b1103b86 (diff)
downloadopencode-d983b9485d91306da6ca72d726348812840f2831.tar.gz
opencode-d983b9485d91306da6ca72d726348812840f2831.zip
fix: add doom loop detection (#3445)
Co-authored-by: Aiden Cline <[email protected]>
-rw-r--r--packages/opencode/src/session/prompt.ts27
-rw-r--r--packages/tui/internal/components/chat/message.go12
2 files changed, 35 insertions, 4 deletions
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 43bb73707..bcab7c847 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -56,6 +56,7 @@ export namespace SessionPrompt {
const log = Log.create({ service: "session.prompt" })
export const OUTPUT_TOKEN_MAX = 32_000
const MAX_RETRIES = 10
+ const DOOM_LOOP_THRESHOLD = 3
export const Event = {
Idle: Bus.event(
@@ -1068,6 +1069,32 @@ export namespace SessionPrompt {
metadata: value.providerMetadata,
})
toolcalls[value.toolCallId] = part as MessageV2.ToolPart
+
+ const parts = await Session.getParts(assistantMsg.id)
+ const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD)
+ if (
+ lastThree.length === DOOM_LOOP_THRESHOLD &&
+ lastThree.every(
+ (p) =>
+ p.type === "tool" &&
+ p.tool === value.toolName &&
+ p.state.status !== "pending" &&
+ JSON.stringify(p.state.input) === JSON.stringify(value.input),
+ )
+ ) {
+ await Permission.ask({
+ type: "doom-loop",
+ pattern: value.toolName,
+ sessionID: assistantMsg.sessionID,
+ messageID: assistantMsg.id,
+ callID: value.toolCallId,
+ title: `Possible doom loop: "${value.toolName}" called ${DOOM_LOOP_THRESHOLD} times with identical arguments`,
+ metadata: {
+ tool: value.toolName,
+ input: value.input,
+ },
+ })
+ }
}
break
}
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index fc5a21ad1..801545a88 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -504,7 +504,11 @@ func renderToolDetails(
base := styles.NewStyle().Background(backgroundColor)
text := base.Foreground(t.Text()).Bold(true).Render
muted := base.Foreground(t.TextMuted()).Render
- permissionContent = "Permission required to run this tool:\n\n"
+ if permission.Type == "doom-loop" {
+ permissionContent = permission.Title + "\n\n"
+ } else {
+ permissionContent = "Permission required to run this tool:\n\n"
+ }
permissionContent += text(
"enter ",
) + muted(
@@ -642,9 +646,9 @@ func renderToolDetails(
for _, item := range todos.([]any) {
todo := item.(map[string]any)
content := todo["content"]
- if content == nil {
- continue
- }
+ if content == nil {
+ continue
+ }
switch todo["status"] {
case "completed":
body += fmt.Sprintf("- [x] %s\n", content)