Skip to content

fix(flows): trail-off backstop no longer clobbers real questions#4956

Merged
graycyrus merged 5 commits into
tinyhumansai:mainfrom
graycyrus:fix/flows-trailoff-question-detection
Jul 16, 2026
Merged

fix(flows): trail-off backstop no longer clobbers real questions#4956
graycyrus merged 5 commits into
tinyhumansai:mainfrom
graycyrus:fix/flows-trailoff-question-detection

Conversation

@graycyrus

@graycyrus graycyrus commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • P0 regression fix, root-caused by an architect review and confirmed against a live tester report: the flows_build terminal-state backstop from fix(flows): guarantee a terminal state on every flows_build turn (no silent trail-off) #4887 was clobbering the agent's real, specific question with a generic fallback whenever text_looks_like_question false-negatived — which it did on an extremely common LLM output shape.
  • Fires 3x in a row on the same user before the tester gave up retrying.

Problem

text_looks_like_question (in src/openhuman/flows/ops.rs) only detected a ? at the very end of the text, or on the last non-blank line. A real tester's agent asked:

"Alan — what's your Slack user ID (the U... code) so I can DM you the daily update? You can find it in Slack under Profile > Copy member ID."

Here the ? sits mid-sentence, immediately followed by a trailing instructional sentence on the same paragraph — a shape LLMs produce constantly ("What's X? You can find it at Y."). The detector missed it, so the trail-off backstop at flows_build (~ops.rs:4368) fired and replaced the agent's specific 538-char question with a generic 146-char fallback ("I wasn't able to finish building this workflow... Could you tell me how you'd like me to resolve that...").

Solution

Two changes:

1. Loosen text_looks_like_question — after the existing terminal-? / last-line checks fail, scan the LAST non-empty paragraph (split on a blank line) for a ? that is NOT inside inline code or a fenced code block. Added a small question_mark_outside_code helper that tracks a running backtick count and treats a ? as "outside code" when the count so far is even (this correctly handles both single-backtick inline spans and triple-backtick fences, since either toggles the cumulative parity by an odd amount at open and close).

While fixing this I found a related, subtler bug in the existing trailing-noise strip: trim_end_matches([...,'',...])was stripping a trailing backtick unconditionally, which could peel the closing delimiter off a code span whose last character was?(e.g. ``id = ?`` at the very end of the text), exposing that?` as if it were a bare trailing question mark and defeating the new code guard entirely. Fixed by excluding the backtick from that noise-trim set — the function doc now explains why.

2. Make the fallback non-destructive. Even with the loosened detector, a future false negative is always possible — the fix should make that failure mode harmless, not just rarer. When the backstop fires, it now prepends the fallback question to the model's original text (format!("{fallback}\n\n---\n\n{original}")) via a new combine_trail_off_fallback helper, instead of discarding the original with assistant_text = fallback.

I chose PREPEND over a REPLACE-with-append because the fallback is the actionable, guaranteed-to-be-a-question part the UI/user needs first; the model's own words are still fully preserved below a --- divider (renders as a horizontal rule in the chat markdown) for context, rather than being silently dropped. If the original text is empty/whitespace-only (a genuine silent turn — nothing to preserve), combine_trail_off_fallback returns the bare fallback rather than prepending an empty divider.

Together: a real question is (almost) never misdetected as non-question (Change 1), and even if a future edge case still misfires, the model's message is never destroyed (Change 2) — only added to.

Testing

  • GGML_NATIVE=OFF cargo check — clean.
  • cargo test --lib openhuman::flows:: — 391 passed, 0 failed.
  • cargo fmt --check — clean.

New/updated tests in src/openhuman/flows/ops_tests.rs:

  • text_looks_like_question_accepts_false_negative_on_trailing_pleasantry — updated to assert the trailing-pleasantry/instruction pattern (same paragraph, e.g. "...to? Let me know!") is now DETECTED (true). Documents that a pleasantry in a separate paragraph ("...to?\n\nLet me know!") remains an intentional, documented false negative — the scan only looks at the last non-blank paragraph by design.
  • text_looks_like_question_detects_mid_sentence_question_with_trailing_instruction (new) — the tester's exact shape, asserts true.
  • text_looks_like_question_ignores_question_mark_inside_code (new) — a ? only inside inline code or a fenced code block is NOT treated as a question; covers both the inline-code and fenced-code-block form of the trailing-backtick edge case found while fixing this.
  • text_looks_like_question_rejects_status_dumps_and_silence (existing, unchanged) — a genuine no-question status dump still returns false, so the backstop still fires for real silence.
  • combine_trail_off_fallback_preserves_original_text_on_genuine_non_question (new) — asserts the model's original text survives in the combined output when the backstop fires.
  • combine_trail_off_fallback_returns_fallback_alone_for_genuine_silence (new) — no empty --- divider prepended when there's nothing to preserve.
  • All existing trail-off / build_trail_off_fallback tests remain green, unmodified.

Acceptance

  • text_looks_like_question detects the tester's exact regression shape (mid-sentence ? + trailing instruction, same paragraph)
  • A ? inside inline code / a fenced code block is still correctly ignored
  • A genuine status dump with no ? still returns false (backstop still fires for real silence)
  • The trail-off fallback is non-destructive — the model's original text is preserved when the backstop fires
  • All existing trail-off tests remain green
  • cargo check, cargo test --lib openhuman::flows::, and cargo fmt --check all clean

Summary by CodeRabbit

  • Bug Fixes

    • Preserved the assistant’s original response when a fallback question is added.
    • Improved question detection by ignoring question marks in code, fenced snippets, and URL parameters.
    • More accurately identifies questions in the final paragraph, including those followed by additional text.
    • Ensured silent responses receive only the appropriate fallback question.
  • Tests

    • Added regression coverage for fallback text preservation and expanded question-detection scenarios.

…al-paragraph detection + non-destructive fallback)

PR tinyhumansai#4887's flows_build terminal-state backstop replaced the agent's final
message with a generic fallback whenever text_looks_like_question()
false-negatived. That detector only checked for a trailing `?` at the very
end of the text / last line, so it missed the extremely common LLM pattern
"What's X? You can find it at Y." — a real question immediately followed by
a trailing instructional sentence. A live tester's agent asked a specific,
answerable question in exactly this shape and had it clobbered by the
generic fallback three times in a row.

Two changes:
- text_looks_like_question now also scans the last non-blank paragraph for
  a `?` outside inline code / a fenced code block, catching the mid-sentence
  + trailing-instruction shape without false-positiving on code samples.
- The backstop's fallback is no longer destructive: when it fires, the
  guaranteed fallback question is prepended to the model's original text
  (divided by a `---`) instead of discarding it, so even a future
  false-negative can never destroy the model's words — only add a
  guaranteed question on top. A genuinely empty/whitespace-only original
  still yields the bare fallback (no empty divider).
@graycyrus
graycyrus requested a review from a team July 16, 2026 11:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2dc2cc9e0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/openhuman/flows/ops.rs Outdated
for ch in text.chars() {
match ch {
'`' => backtick_count += 1,
'?' if backtick_count % 2 == 0 => return true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a real question before suppressing fallback

When the builder's final paragraph is a status/update containing a non-question ? outside backticks, such as a URL with a query string (Checked https://api.example/search?q=foo and got 403.), this branch returns true solely because the character is outside code. flows_build then skips combine_trail_off_fallback, so the terminal-state backstop can still leave the user with a non-answerable status note and no proposal/question.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in c09ec76: each candidate ? outside code now also has to be sentence-terminal (whitespace/EOS after it, skipping closing quote/bracket punctuation) via a new is_sentence_terminal_question_mark helper, so a mid-token ? like a URL query string no longer flips text_looks_like_question to true. Added a regression test (text_looks_like_question_ignores_question_mark_in_url_query_string) covering your exact example plus a bare-? query-param case.

…on (addresses @chatgpt-codex-connector on src/openhuman/flows/ops.rs:4486)

The final-paragraph scan added for the mid-sentence question shape
counted ANY `?` outside code as a question, including one embedded
mid-token (a URL query string like `search?q=foo`, or similar
non-prose use). That made text_looks_like_question() false-positive
on status dumps like "Checked https://api.example/search?q=foo and
got 403.", which skips combine_trail_off_fallback() entirely and
leaves the user with an unanswerable status note — the exact failure
mode this backstop exists to prevent.

Require each candidate `?` to be sentence-terminal (followed by
whitespace/EOS, skipping over closing quote/bracket punctuation)
before it counts. Also fixes the CI clippy failure on this line
(manual_is_multiple_of) that landed in the same match arm.
…scope

- Rename text_looks_like_question_accepts_false_negative_on_trailing_pleasantry
  to text_looks_like_question_detects_same_paragraph_trailing_pleasantry —
  the old name described the pre-fix behavior (an accepted false
  negative); post-fix this shape is correctly detected.
- Add a companion test pinning the still-intentional cross-paragraph
  false negative (a `?` in an earlier paragraph than the last one is
  not detected, by design — the non-destructive fallback makes this
  safe).
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4fb5dcd5-82f4-40f8-8cff-3090aadbafcd

📥 Commits

Reviewing files that changed from the base of the PR and between c09ec76 and 60484ac.

📒 Files selected for processing (2)
  • src/openhuman/flows/ops.rs
  • src/openhuman/flows/ops_tests.rs
📝 Walkthrough

Walkthrough

Trail-off handling now preserves non-question assistant text alongside generated fallback questions. Question detection scans terminal paragraphs while ignoring code and URL query marks, with expanded regression coverage.

Changes

Trail-off fallback handling

Layer / File(s) Summary
Code-aware question detection
src/openhuman/flows/ops.rs, src/openhuman/flows/ops_tests.rs
Question detection now evaluates sentence-terminal marks in the final paragraph while ignoring inline code, fenced code, and URL query strings.
Fallback text preservation
src/openhuman/flows/ops.rs, src/openhuman/flows/ops_tests.rs
Trail-off fallbacks preserve non-empty original text after a divider and return only the fallback for empty or whitespace-only input.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: codeghost21

Poem

I’m a rabbit, and I thump with delight,
Fallback words now keep the old text in sight.
Code marks stay quiet, URLs don’t deceive,
Questions bloom where sentence endings leave.
Hop, hop—tests guard the trail tonight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main fix: the trail-off backstop no longer overwrites real questions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/flows/ops_tests.rs`:
- Around line 4960-4975: The test
combine_trail_off_fallback_preserves_original_text_on_genuine_non_question
currently verifies presence but not ordering or separation. Replace the two
contains assertions with an exact equality assertion for the documented
fallback-first combined format, including the required divider and both fallback
and original texts; retain the question-invariant assertion.

In `@src/openhuman/flows/ops.rs`:
- Around line 4466-4470: Update the paragraph detection around
question_mark_outside_code to split text using lines() and blank-line
boundaries, treating whitespace-only separators and CRLF input as separate
paragraphs. Preserve the reverse search for the final non-empty paragraph so
only a terminal question satisfies the guarantee, and add regressions covering
whitespace-only and CRLF paragraph separators.
- Around line 4491-4504: Update question_mark_outside_code to track Markdown
backtick delimiter runs and their lengths, rather than treating any even
backtick count as outside code. Match closing runs to the active opening fence
length so embedded backticks and double-backtick spans remain inside code; add
tests covering double-backtick fences and embedded backticks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38c97147-7dd3-4900-9df0-46d5179ef5a3

📥 Commits

Reviewing files that changed from the base of the PR and between 30b05d8 and c09ec76.

📒 Files selected for processing (2)
  • src/openhuman/flows/ops.rs
  • src/openhuman/flows/ops_tests.rs

Comment thread src/openhuman/flows/ops_tests.rs
Comment thread src/openhuman/flows/ops.rs Outdated
Comment thread src/openhuman/flows/ops.rs
…esses @coderabbitai on ops.rs:4470, ops.rs:4504)

Two correctness fixes to the trail-off question detector from the
last commit, both flagged by CodeRabbit:

- Final-paragraph splitting used the literal "\n\n" byte sequence,
  which mishandles CRLF text ("question?\r\n\r\nstatus") and
  whitespace-only blank-line separators ("question?\n \nstatus") —
  both get treated as ONE paragraph, so an earlier question can leak
  into and suppress the fallback for a later, non-question status
  paragraph. Replaced with a new `last_paragraph` helper that walks
  `str::lines()` (which normalizes CRLF) and treats any all-whitespace
  line as a paragraph break.

- `question_mark_outside_code` tracked a running per-character
  backtick COUNT and used its parity to decide "inside/outside a
  span." That misclassifies any code span whose delimiter is more
  than one backtick (e.g. ``SELECT ? FROM t`` opens with a 2-backtick
  run, count 0->2 is even, so it looks "outside" again immediately) —
  the `?` inside a valid double-backtick span was wrongly counted as a
  real question mark. Rewrote to track backtick delimiter RUN LENGTH
  per CommonMark's code-span rule: a run of N backticks opens a span
  that only the next run of exactly N backticks closes; mismatched
  runs are literal characters within the span.
… assertion

- Assert the exact combined string in
  combine_trail_off_fallback_preserves_original_text_on_genuine_non_question
  instead of two `contains` checks, so the test actually pins the
  documented fallback-first ordering and `---` divider (addresses
  @coderabbitai on ops_tests.rs:4975).
- Add text_looks_like_question_treats_crlf_and_whitespace_lines_as_paragraph_breaks
  covering both CRLF and whitespace-only paragraph separators
  (addresses @coderabbitai on ops.rs:4470).
- Add text_looks_like_question_ignores_question_mark_inside_double_backtick_span
  covering a double-backtick span and one with an embedded single
  backtick (addresses @coderabbitai on ops.rs:4504).
@graycyrus
graycyrus merged commit 3d76d12 into tinyhumansai:main Jul 16, 2026
6 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

1 participant