Skip to content

fix(loop-guard): O96 do not treat lint/test non-zero exits as broken exec - #97

Merged
QiaolongLi1201 merged 4 commits into
mainfrom
fix/o96-loop-guard-exec-exit
Oct 11, 2026
Merged

QiaolongLi1201 merged 4 commits into
mainfrom
fix/o96-loop-guard-exec-exit

Conversation

@QiaolongLi1201

Copy link
Copy Markdown
Collaborator

Summary

O96 — loop-guard false positive: normal exec / run_tests non-zero exits (lint/test/check failed) must not count as tool failures and must not produce STOP calling exec / tool unavailable.

  • classifyShellToolFailure: classify by result facts only (normal_nonzero vs timeout / start_failure / killed / unusable / other)
  • recordToolLoopOutcome: exec / run_tests normal_nonzero skips byToolFailure; tracks bySameCommandFailure (normalized command + output fingerprint)
  • Same-command identical failures ≥3 → change-approach hint (改代码或改命令); never STOP / 工具不可用
  • Real faults (timeout / failed to start / killed) still block at existing limit; messages say 超时 / 未能启动
  • Does not raise DEFAULT_TOOL_FAILURE_LIMIT; does not touch src/safety; discovery / edit thrash / web_fetch rules unchanged

Spec

/workspace/specs/O96.md (PM + UX + QA sections). Grok draft: /workspace/specs/O96-grok.md.

Revert-red (QA #1 / 验收 D)

Simulated reverting “normal_nonzero skips byToolFailure” (still byToolFailure +1 on that path):

Result Count
fail (turns red) 5 (A, A-run_tests, C pollution assert, QA isolation, C different-output scenario hit by global stop)
pass (stays green) 7 (includes B timeout, B start failure, classify, blocked guardrail, normalize, exit0 clears streak, UX same-command copy)

Verified: A goes red; B stays green.

Test plan

  • node --test test/o96-loop-guard-exec-exit.spec.mjs → 12/12 pass
  • Related loop-guard + string-tool-failure + exec-write-lock (+ o96) → 55/55 pass
  • npm run check (format / lint / typecheck) → green
  • Revert-red A red / B green (see above)
  • Full npm test / CI
  • UX: copy spot-check + DeepSeek first-install ×5 when key available (does not block merge gate for this PR)

Files

File Role
src/core/tools/tool-loop-guard.ts O96 classify + same-command counter + ZH/EN copy
test/o96-loop-guard-exec-exit.spec.mjs new A–C + QA/UX asserts

Base: c71cd084 (post-O99 #96). Head: 72b89f1d. No src/safety changes. Do not merge until QA/UX sign.

Normal completed exec/run_tests non-zero exits (lint/test fail) no longer
increment byToolFailure or trigger STOP calling it. Timeouts, start
failures, kills, and unusable empty output still count. Same-command
identical-output streaks use a separate counter with a change-code/command
hint (zh+en). Does not touch src/safety or raise failure limits.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Production exec ProcessError timeouts return "timed out after Nms" without
an Error: prefix, so is_error stayed false and loop-guard treated them as
success — falling into the identical-input generic message without 超时.

- isStringToolFailureResult: recognize timed out after / Command killed by
- recordToolLoopOutcome: classify VERIFY_SHELL faults even when isError=false
- e2e: production-shaped + real execTool timeout through executeOneToolCall
…or=false text scan

Child stdout containing 'timed out after' / 'killed by SIG' / ENOENT /
'command not found' was misclassified as a tool fault. Only trust is_error
from execute-tool-call's anchored isStringToolFailureResult; exit_code: N
header => normal_nonzero regardless of stdout. Adds 4 negative tests (QA).
…ark exec timeout/kill structurally

- classifyShellToolFailure: first-line exit_code 126/127 (and Command failed
  (exit 126|127)) => start_failure (未能启动). Stdout never classifies.
- Block copy names the missing command (parsed from shell 'not found' output,
  display-only, [A-Za-z0-9._/-]{1,64}) with next step 装依赖或改命令; otherwise
  generic 未能启动.
- Remove 'timed out after' / 'Command killed by' text regexes from
  isStringToolFailureResult; exec's ProcessError timeout/signal branch calls
  ctx.markResultError() so is_error comes from process facts, not stdout.
@QiaolongLi1201
QiaolongLi1201 merged commit 465cbaa into main Oct 11, 2026
6 of 12 checks passed
@QiaolongLi1201
QiaolongLi1201 deleted the fix/o96-loop-guard-exec-exit branch October 11, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant