fix: detect completion signal in any XML tag, not just <promise> (#1126)#1184
fix: detect completion signal in any XML tag, not just <promise> (#1126)#1184
Conversation
Loop nodes with `until:` reported max_iterations_reached when the AI wrapped the completion signal in XML tags other than `<promise>` (e.g., `<COMPLETE>ALL_CLEAN</COMPLETE>`). The three existing regex patterns all missed this format, causing the loop to exhaust iterations and fail. Changes: - Add generic XML-wrapped signal pattern to `detectCompletionSignal` - Extend `stripCompletionTags` to strip matched XML-wrapped signals from output - Pass `loop.until` to `stripCompletionTags` call site in dag-executor - Add unit tests for detection and stripping of XML-wrapped signals - Add integration test for loop completing on final iteration with XML tags Fixes #1126
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR enhances completion signal detection in loop nodes to recognize XML-wrapped formats (e.g., Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 Comprehensive PR ReviewPR: #1184 — fix: detect completion signal in any XML tag, not just SummaryMinimal, well-scoped fix (3 source lines changed). The implementation is correct: Verdict: ✅
🟡 Medium Issues (Quick Fixes)1. JSDoc for
|
| Issue | Location | Suggestion |
|---|---|---|
Superfluous m flag on xmlWrappedPattern — no anchors, no effect |
executor-shared.ts:390 |
Change 'im' → 'i' to match strip path's 'gi' |
Tag mismatch: <foo>SIGNAL</bar> triggers detection (intentional but undocumented) |
executor-shared.ts:390 |
Add comment: // Note: opening and closing tag names are not required to match |
| Per-chunk strip vs full-output detect: split XML tag could produce tag fragments in stream | dag-executor.ts:1597 |
Pre-existing <promise> behavior; accept and document |
| Tag-mismatch permissive behavior not pinned in tests | executor-shared.test.ts |
Add it('detects signal in mismatched XML tags (permissive)', ...) |
| DAG integration test doesn't assert user-visible output is clean | dag-executor.test.ts:2930 |
Assert platform.sendMessage calls contain no <COMPLETE> |
xmlWrappedPattern comment doesn't note tag-name independence |
executor-shared.ts:390 |
Append note to existing comment |
stripCompletionTags JSDoc doesn't mention until param |
executor-shared.ts:403 |
Append clause or add @param until |
✅ What's Good
escapeRegExpapplied to user-suppliedsignalin both detect and strip — correct defense against regex injection from workflow YAMLuntil?is fully backward-compatible; all existing call sites unaffected- Tests cover the most important negative scenarios: wrong value in tags, signal embedded in prose (guarding the primary false-positive risk that motivated the XML format)
- DAG integration test verifies full execution path:
completeWorkflowRuncalled once,failWorkflowRunnever called - Detection and stripping use identical underlying regex — no drift between what is detected and what is removed
- Fix is precisely scoped — no cleanup, no refactoring, no speculative features
Reviewed by Archon comprehensive-pr-review workflow
- Update detectCompletionSignal JSDoc to document all three detection formats - Update stripCompletionTags JSDoc to mention the `until` parameter - Remove superfluous `m` flag from xmlWrappedPattern (no anchors, no effect) - Document that XML tag names are matched independently (intentional permissiveness) - Add test: detects signal in mismatched XML tags (permissive behavior) - Add test: strips both <promise> and XML-tagged signal in same chunk - Add assertion in DAG integration test that raw XML tags don't appear in sent messages
⚡ Self-Fix Report (Aggressive)Status: COMPLETE Fixes Applied (7 total)
View all fixes
Tests Added
Skipped (1)
Validation✅ Type check | ✅ Lint | ✅ Tests (47 passed executor-shared, 154 passed dag-executor) Self-fix by Archon · aggressive mode · fixes pushed to |
Follow-up to the initial broadening in this PR. The first version of the regex accepted mismatched open/close tags (e.g. `<COMPLETE>X</done>`) which was a small false-positive surface when the AI interleaves tags in prose. Tightens both detectCompletionSignal and stripCompletionTags to capture the tag name and enforce it on the close via \1 backreference. Case-insensitivity on the tag name is preserved. Test updates: - Flip the "permissive mismatch" case to assert strict rejection with a comment explaining the guard. - Add a case-insensitive matching case to lock that behavior in. No behavior change for workflows that use matching tags (the overwhelming common case) or for <promise>...</promise>. Behavior change is limited to the narrow "open tag and close tag disagree" case, which only happens when the AI is confused — in which case we'd rather report max_iterations_reached and let the author inspect than silently call the loop complete.
Summary
until:fail when the AI wraps the completion signal in any XML tag other than<promise>(e.g.<COMPLETE>ALL_CLEAN</COMPLETE>), causingmax_iterations_reachedeven though the signal was presentdetectCompletionSignalnow matches<anyTag>SIGNAL</anyTag>in addition to<promise>SIGNAL</promise>and plain-text patterns;stripCompletionTagsaccepts an optionaluntilparam to strip matched XML tags from user-visible output<promise>format, plain-text signal detection,max_iterations_reachederror message, and all other executor logicUX Journey
Before
After
Architecture Diagram
Before
After
Connection inventory:
dag-executor.tsdetectCompletionSignaldag-executor.tsstripCompletionTagsloop.untilas second argexecutor-shared.tsdetectCompletionSignalimplexecutor-shared.tsstripCompletionTagsimpluntilparamLabel Snapshot
risk: lowsize: XSworkflowsworkflows:executorChange Metadata
bugworkflowsLinked Issue
Validation Evidence (required)
--max-warnings 0)Security Impact (required)
The added regex pattern matches only within already-captured AI output strings; no new attack surface.
Compatibility / Migration
The
stripCompletionTagssignature change is backward compatible:untilis optional with defaultundefined, so all existing call sites compile and behave identically.Human Verification (required)
<COMPLETE>ALL_CLEAN</COMPLETE>detected and signal-stripped from output<promise>COMPLETE</promise>format still works"not COMPLETE yet") still not detected (false-positive guard)<COMPLETE>WRONG</COMPLETE>for signalALL_CLEAN) not detected<COMPLETE/>) do not match (no content)Side Effects / Blast Radius (required)
until:completion signals only<code>SIGNAL_VALUE</code>in a code block andSIGNAL_VALUEhappens to match theuntil:signal. This is a pre-existing risk with plain detection as well, and is mitigated by using distinctive signal values.Rollback Plan (required)
executor-shared.tsand one changed line indag-executor.tsmax_iterations_reachedwhen the AI uses non-<promise>XML tags; no data loss or state corruptionRisks and Mitigations
<tag>SIGNAL</tag>in a code example inside its responseALL_CLEAN,DONE). This is consistent with existing guidance and the pre-existing plain-text detection risk.Summary by CodeRabbit
Bug Fixes
Tests