fix(runner): fix fingerprint collision on projectId and relax loop-break limits

This commit is contained in:
2026-06-03 13:58:56 -07:00
parent d7206ea2ee
commit fa8a919214
2 changed files with 64 additions and 37 deletions

View File

@@ -300,21 +300,30 @@ File: "${path.relative(opts.repoRoot ?? ctx.workspaceRoot, task.filePath)}" (lin
const toolFingerprints = [];
let ralphIteration = 0;
function fingerprintToolCall(tc) {
if (tc.name === "shell_exec") {
const cmd = String(tc.args?.command ?? "").trim();
const verb = cmd
.split("&&")
.map((s) => s.trim())
.find((s) => !s.startsWith("cd "))
?.split(/\s+/)[0] ?? "shell";
return `shell_exec:${verb}`;
const name = tc.name;
const args = tc.args ?? {};
if (name === "shell_exec") {
const cmd = String(args.command ?? "").trim();
const firstWord = cmd.split(/\s+/)[0] ?? "shell";
return `shell_exec:${firstWord}`;
}
if (tc.name === "fs_write" ||
tc.name === "fs_edit" ||
tc.name === "fs_read") {
return `${tc.name}:${tc.args?.path}`;
// Determine target based on most common descriptive parameter keys
const target = args.path ??
args.pattern ??
args.command ??
args.commandId ??
args.appUuid ??
args.uuid ??
"";
if (target) {
return `${name}:${target}`;
}
return `${tc.name}:${Object.values(tc.args ?? {})[0]}`;
// Filter out common metadata like projectId, and use first real argument
const keys = Object.keys(args).filter((k) => k !== "projectId");
if (keys.length > 0) {
return `${name}:${args[keys[0]]}`;
}
return `${name}:default`;
}
while (subTurn < SUB_MAX_TURNS) {
if (opts.isStopped()) {
@@ -421,20 +430,23 @@ ${verification.error}
for (const tc of resp.toolCalls) {
toolFingerprints.push(fingerprintToolCall(tc));
}
const window = toolFingerprints.slice(-6);
const window = toolFingerprints.slice(-12);
const counts = new Map();
for (const fp of window)
counts.set(fp, (counts.get(fp) ?? 0) + 1);
let maxRepeats = 0;
let repeatedCmd = "";
for (const [fp, n] of counts.entries()) {
if (n > maxRepeats)
if (n > maxRepeats) {
maxRepeats = n;
repeatedCmd = fp;
}
if (maxRepeats >= 4) {
}
if (maxRepeats >= 6) {
await emit({
ts: now(),
type: "error",
text: `Loop detected in subtask execution, breaking loop.`,
text: `Loop detected in subtask execution (repeated "${repeatedCmd}" ${maxRepeats}x in last 12 calls), breaking loop.`,
});
return false;
}

View File

@@ -403,24 +403,35 @@ File: "${path.relative(opts.repoRoot ?? ctx.workspaceRoot, task.filePath)}" (lin
let ralphIteration = 0;
function fingerprintToolCall(tc: any) {
if (tc.name === "shell_exec") {
const cmd = String(tc.args?.command ?? "").trim();
const verb =
cmd
.split("&&")
.map((s) => s.trim())
.find((s) => !s.startsWith("cd "))
?.split(/\s+/)[0] ?? "shell";
return `shell_exec:${verb}`;
const name = tc.name;
const args = tc.args ?? {};
if (name === "shell_exec") {
const cmd = String(args.command ?? "").trim();
const firstWord = cmd.split(/\s+/)[0] ?? "shell";
return `shell_exec:${firstWord}`;
}
if (
tc.name === "fs_write" ||
tc.name === "fs_edit" ||
tc.name === "fs_read"
) {
return `${tc.name}:${tc.args?.path}`;
// Determine target based on most common descriptive parameter keys
const target =
args.path ??
args.pattern ??
args.command ??
args.commandId ??
args.appUuid ??
args.uuid ??
"";
if (target) {
return `${name}:${target}`;
}
return `${tc.name}:${Object.values(tc.args ?? {})[0]}`;
// Filter out common metadata like projectId, and use first real argument
const keys = Object.keys(args).filter((k) => k !== "projectId");
if (keys.length > 0) {
return `${name}:${args[keys[0]]}`;
}
return `${name}:default`;
}
while (subTurn < SUB_MAX_TURNS) {
@@ -536,20 +547,24 @@ ${verification.error}
for (const tc of resp.toolCalls) {
toolFingerprints.push(fingerprintToolCall(tc));
}
const window = toolFingerprints.slice(-6);
const window = toolFingerprints.slice(-12);
const counts = new Map<string, number>();
for (const fp of window) counts.set(fp, (counts.get(fp) ?? 0) + 1);
let maxRepeats = 0;
let repeatedCmd = "";
for (const [fp, n] of counts.entries()) {
if (n > maxRepeats) maxRepeats = n;
if (n > maxRepeats) {
maxRepeats = n;
repeatedCmd = fp;
}
}
if (maxRepeats >= 4) {
if (maxRepeats >= 6) {
await emit({
ts: now(),
type: "error",
text: `Loop detected in subtask execution, breaking loop.`,
text: `Loop detected in subtask execution (repeated "${repeatedCmd}" ${maxRepeats}x in last 12 calls), breaking loop.`,
});
return false;
}