Skip to content

fix(proxy): wire no-progress detector into OpenAI chat completions#826

Open
rohith500 wants to merge 2 commits into
workweave:mainfrom
rohith500:fix/openai-noprogress-825
Open

fix(proxy): wire no-progress detector into OpenAI chat completions#826
rohith500 wants to merge 2 commits into
workweave:mainfrom
rohith500:fix/openai-noprogress-825

Conversation

@rohith500

Copy link
Copy Markdown
Contributor

Summary

Fixes #825: ProxyOpenAIChatCompletion now runs the same cross-envelope no-progress detector that ProxyMessages already has (5 identical dispatch fingerprints within 90s → synthetic stop via handleNoProgressBreak).

  • Reuses existing computeNoProgressFingerprint / recordAndDetect / handleNoProgressBreak — including the previously production-dead FormatOpenAI synthetic chat.completion branch.
  • Gates match Messages: !agentShadowMode && !AuthoritativePerTurn && toolBearingTurn && noProgress != nil.
  • OpenAI tool-bearing detection uses the same envelope helpers (AssistantToolCallSignatures + LastUserMessage().HasToolResult for trailing role=tool messages), snapshotted before compaction like Messages.
  • ProxyOpenAIResponses still delegates to ProxyOpenAIChatCompletion (confirmed), so Responses ingress inherits this automatically. Gemini out of scope.

Test plan

  • Commit 1 (red): TestProxyOpenAIChatCompletion_NoProgressBreaksAtThreshold failed with brokeAt=0, upstreamHits=6
  • Commit 2 (green): same test passes; breaks on attempt 5 with No-progress loop detected
  • TestHandleNoProgressBreak_OpenAIChatCompletionShape — asserts object=chat.completion, assistant/stop, prompt_tokens
  • Negative control: varying tool args ×6 — no break, 6 upstream hits
  • Existing TestNoProgress* suite still green (Messages path unchanged)
  • make check passed
  • Adversarially verified (not committed): text-only identical ×6 no
    break, 4-below-threshold no break, 5-across-expired-window no break,
    same-messageCount-different-content no false collision, 16-concurrent
    no panic

Before / after (positive control)

Before (commit 1 only):

--- FAIL: TestProxyOpenAIChatCompletion_NoProgressBreaksAtThreshold
    Error: Not equal: expected: 5 actual: 0
    Messages: ProxyOpenAIChatCompletion must break on the 5-th identical fingerprint (got brokeAt=0, upstreamHits=6)

After (commit 2):

=== RUN   TestProxyOpenAIChatCompletion_NoProgressBreaksAtThreshold
... "message":"No-progress loop detected; breaking turn" ... "repeat_count":5 ...
--- PASS: TestProxyOpenAIChatCompletion_NoProgressBreaksAtThreshold

Follow-up (non-blocking)

With no pin store configured, both Messages and OpenAI fall back to a
shared install:<id>:<role> fingerprint bucket (pre-existing coarseness
for Messages alone; now also shared with OpenAI). In production with a
pin store, keys are per-session via DeriveSessionKey, so this only
matters for installations running without one. Not a regression this PR
introduces — the install-bucket fallback already existed — just newly
shared across two surfaces. Worth a follow-up look if pin-store-less
installations turn out to be common enough to matter; not blocking this
fix.

rohith500 and others added 2 commits July 23, 2026 16:21
Add the failing positive-control for ProxyOpenAIChatCompletion (identical
tool-bearing fingerprints must break at threshold 5), a varying-tools
negative control, and coverage for handleNoProgressBreak's FormatOpenAI
chat.completion shape.

Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
ProxyOpenAIChatCompletion now runs the same cross-envelope no-progress
gate as ProxyMessages after routing, making handleNoProgressBreak's
FormatOpenAI branch reachable in production. Fixes workweave#825.

Signed-off-by: N Rohith Reddy <rohithreddy2202@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

PR author is not in the allowed authors list.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Thanks for this, @rohith500 — really clean contribution. Reviewed against the root AGENTS.md/CLAUDE.md and internal/proxy/CLAUDE.md, and it follows the repo conventions nicely, so no changes needed on my end:

  • Right layer. The detector is wired on *proxy.Service.ProxyOpenAIChatCompletion (the routing/dispatch home per internal/proxy/CLAUDE.md), with no new I/O — it reuses the existing computeNoProgressFingerprint / recordAndDetect / handleNoProgressBreak machinery rather than duplicating it (DRY, root AGENTS.md).
  • Faithful parity with ProxyMessages. Same gate (!agentShadowMode && !AuthoritativePerTurn && toolBearingTurn && noProgress != nil), same tool-bearing detection via AssistantToolCallSignatures + trailing role=tool HasToolResult, snapshotted before compaction — a good instance of "put logic in the package that uses it" without re-inventing it.
  • No magic strings in prod code — routing goes through catalog.TierFor/roleForTier, and the proxy_test file uses providers.ProviderOpenAI/providers.ProviderAnthropic constants (root AGENTS.md → Conventions → Go style).
  • Non-tautological tests — the positive control asserts the exact break attempt + upstream hit count + chat.completion JSON shape, and the negative control (varying tool args) proves it doesn't trip; both would fail if the prod wiring were removed (root AGENTS.md → Tests). Nice touch keeping them as in-memory fake-repo tests rather than DB-backed integration tests in internal/.
  • Comments are concise/why-focused and the #825 guard refs match the established convention in this package (cf. conformance_responses_test.go #331, enabled_providers_internal_test.go PR #159).

CI is green. LGTM — thanks again!

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.

ProxyOpenAIChatCompletion never runs the cross-envelope no-progress detector

1 participant