Skip to content

fix(quick-router): "turn the oven on" is answered with a status read instead of actuating - #878

Merged
matedev01 merged 3 commits into
GeniePod:mainfrom
kai392:fix/critical-issue-oven-command-misroute
Jul 29, 2026
Merged

fix(quick-router): "turn the oven on" is answered with a status read instead of actuating#878
matedev01 merged 3 commits into
GeniePod:mainfrom
kai392:fix/critical-issue-oven-command-misroute

Conversation

@kai392

@kai392 kai392 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #877.

Root cause

home_status_target's oven branch runs ahead of the looks_like_status_query
gate and keys on a bare phrase:

if (text.contains("oven on") || text.contains("leave the oven"))
    && !text.contains("self cleaning")
    && !text.contains("self clean")
{
    return Some("oven".into());
}

So any utterance containing "oven on" resolves to a status read — including a
command. "Turn the oven on" returned home_status{entity:"oven"}, the turn
never reached the LLM, and the oven did not actuate: the user asked for an
action and got a status report.

Every other appliance already abstains on this word order and lets the LLM
ground the write ("Turn the lights on", "Turn the fan on" both fall
through), which is what made the oven the one device where a trailing-particle
command silently failed.

The same predicate also matched oven inside pr[oven] / w[oven] — the
substring problem the cooktop branch below was already fixed for in #792.

Fix

Admit only the two shapes that actually ask about the oven's state, every
token matched whole:

  • is/are the oven … on — the oven is the subject of the question.
  • did … leave the oven … — the past-action check the standard status-query
    prefixes do not cover.

I first gated this on looks_like_status_query(), but review caught that it
still admits a command embedded in a question ("what happens if I turn the oven on", "check if I should turn the oven on"). Anchoring on the oven as the
subject rather than on a bare is /are prefix is what keeps those out —
"are you going to turn the oven on" no longer qualifies either. Matching
leave the oven as three whole tokens also stops "leave the ovenware" from
matching.

One branch changed, plus a regression test. Nothing outside this predicate is
touched.

Impact

Oven actuation commands stop being swallowed by a status read and reach the LLM,
which grounds them as home_control. Three substring false positives go away.
Genuine oven status questions, the self-cleaning entity, and the
set_temperature path are unchanged — all asserted in the new test, and
routes_shopping_and_temperature_home_requests already pins
"Sarah: Did I leave the oven on?". "Is the oven still on?" now resolves too;
it did not on main, because the substring "oven on" is not present.

What this does not fix

The interrogative-hypothetical forms — "what happens if I turn the oven on",
"check if I should turn the oven on" — no longer reach this branch, but they
are still status-shaped by looks_like_status_query, so they fall through to the
cooktop branch and report stove. That is a pre-existing gap in a different
predicate (the same class as #861) and I left it alone rather than widen this
diff; the new test asserts only that they no longer resolve to the oven
entity. The actuation-blocking half — the imperative forms a user actually says
to control the oven — is fully fixed.

Relationship to #862

#862 (issue #861) introduces a status_shaped gate for the bare-appliance
noun
branches — water pressure, sump pump, sous vide, baby monitor, water
heater. It does not touch the "oven on" phrase branch, which is what this
changes; the two diffs are in different hunks and do not conflict. This PR also
carries a whole-word component that #862 has no equivalent of. Happy to rebase
whichever lands second.

Risk / tradeoffs

Low, but worth naming: the gate is a genuine narrowing, so an oven status
question phrased outside the recognized shapes — "the oven still on?" with no
leading is/did — now abstains instead of resolving. That is the same
tradeoff the iron and bare-appliance branches accepted, and abstaining sends the
turn to the LLM rather than producing a wrong answer.

Real Behavior Proof

  • I have built and run the affected code locally (or noted why I could not).
  • I have NOT verified on Jetson hardware, and I explain the equivalent verification path or validation gap below.

Tested profile / hardware:

  • laptop (Windows host — see the gap note below)
  • CI-only / docs-only

What I ran

I do not have a Jetson, and genie-core does not build on my Windows host
(libc::gmtime_r, tokio::signal::unix and friends are Unix-only), so the
routing change was exercised two ways.

1. Direct route() calls. I compiled the four quick-router source files
unmodifiedcrates/genie-core/src/tools/{quick,home_action,calc_input,number_words}.rs,
included by #[path], with only ToolCall and HomeActionKind stubbed — into a
throwaway binary and called tools::quick::route() directly. No logic was
copied or reimplemented.

2. cargo test -p genie-core on Linux via GitHub Actions, plus the rest of
the matrix (cargo fmt, cargo clippy -D warnings, --no-default-features,
and the aarch64-unknown-linux-gnu release build), run on this exact commit
before opening this PR: kai392#3 — all green.
The review follow-up commit was validated the same way:
kai392#4, also green.

test tools::quick::tests::turning_the_oven_on_is_a_command_not_a_status_read ... ok
test result: ok. 966 passed; 0 failed; 6 ignored; 0 measured; 0 filtered out

What I observed

Before, on main @ 9e54db4:

"Turn the oven on"                        -> home_status {"entity":"oven"}
"Switch the oven on"                      -> home_status {"entity":"oven"}
"Please turn the oven on"                 -> home_status {"entity":"oven"}
"Can you turn the oven on?"               -> home_status {"entity":"oven"}
"Remind me to turn the oven on at six"    -> home_status {"entity":"oven"}
"Don't leave the oven on"                 -> home_status {"entity":"oven"}
"Is that theory proven on the test bench?" -> home_status {"entity":"oven"}
"Is the basket woven on a loom?"          -> home_status {"entity":"oven"}

After, same harness, same build:

"Turn the oven on"                        -> (abstain, falls through to the LLM)
"Switch the oven on"                      -> (abstain, falls through to the LLM)
"Please turn the oven on"                 -> (abstain, falls through to the LLM)
"Can you turn the oven on?"               -> (abstain, falls through to the LLM)
"Remind me to turn the oven on at six"    -> (abstain, falls through to the LLM)
"Don't leave the oven on"                 -> (abstain, falls through to the LLM)
"Is that theory proven on the test bench?" -> (abstain, falls through to the LLM)
"Is the basket woven on a loom?"          -> (abstain, falls through to the LLM)

"What happens if I turn the oven on?"      -> home_status {"entity":"stove"} (see note above)
"Are you going to turn the oven on?"       -> home_status {"entity":"stove"} (see note above)
"Did I leave the ovenware on?"             -> (abstain, falls through to the LLM)

"Is the oven on?"                         -> home_status {"entity":"oven"}
"Is the oven still on?"                   -> home_status {"entity":"oven"}
"Did I leave the oven on?"                -> home_status {"entity":"oven"}
"Sarah: Did I leave the oven on?"         -> home_status {"entity":"oven"}
"Is the self-cleaning oven on?"           -> home_status {"entity":"self-cleaning oven"}
"Check the oven"                          -> home_status {"entity":"stove"}
"Set the oven to 400 degrees"             -> home_control {set_temperature, 400}
"Preheat the oven to 350"                 -> home_control {set_temperature, 350}

Validation gap

The routing half is fully covered above. What I could not verify is the
downstream half: that the LLM, now that it sees "turn the oven on", emits
home_control{entity:"oven",action:"turn_on"} and the oven actually actuates
against a live Home Assistant. That needs the model and an HA instance on
device. A maintainer with the deploy/homeassistant/ config up could confirm
the off -> on state change through the HA API.

… read

The oven branch in `home_status_target` keyed on the bare phrase "oven on"
with no question-shape gate, so a command that merely contained it was
answered with a status report: "turn the oven on" and "remind me to turn
the oven on at six" both returned home_status{entity:"oven"} and the oven
never actuated. Every other appliance already abstains on this word order
— "turn the lights on", "turn the fan on" both fall through to the LLM —
so the oven was the one device where a trailing-particle command silently
failed.

The same predicate also matched "oven" inside "pr[oven]" / "w[oven]",
reporting the oven for "is that theory proven on the test bench".

Gate on the status-question shape the iron block already uses, and match
"oven" as a whole word like the cooktop branch below. Genuine status
questions, the self-cleaning entity, and the set_temperature path are
unchanged.
@github-actions github-actions Bot added the bug Something isn't working label Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@matedev01, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4debee69-6d51-48fd-8ed1-5d99ac413dc6

📥 Commits

Reviewing files that changed from the base of the PR and between f0dab0f and 8ed3b2a.

📒 Files selected for processing (1)
  • crates/genie-core/src/tools/quick.rs
📝 Walkthrough

Walkthrough

The oven quick-router now requires whole-word matching and status-question syntax before returning home_status. Regression tests cover activation commands, substring collisions, valid status questions, self-cleaning status, and temperature-setting behavior.

Changes

Oven status routing

Layer / File(s) Summary
Oven status gate and regression coverage
crates/genie-core/src/tools/quick.rs
Oven status detection now uses whole-word oven/on matching plus status-question or did syntax, and tests verify command abstention and existing oven routing paths.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: matedev01, yurii214, michiot05, ultrahighsuper

🚥 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 summarizes the oven quick-router bug fix and is specific to the main change.
Linked Issues check ✅ Passed The change matches #877 by letting oven actuation fall through, preserving status/self-cleaning/temp routes, and blocking substring false positives.
Out of Scope Changes check ✅ Passed Only the oven routing logic and a regression test were changed; no unrelated behavior or exports were introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 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 `@crates/genie-core/src/tools/quick.rs`:
- Around line 2487-2489: Update the oven-routing condition near
asks_about_the_oven so looks_like_status_query is additionally constrained to
explicit oven-state questions, rather than non-status requests that merely
contain adjacent “oven on” text. Preserve the existing “did ” handling and
self-cleaning exclusion, and add regression coverage for “What happens if I turn
the oven on?” and “Check if I should turn the oven on” to ensure they do not
route to home_status.
- Around line 2481-2486: Update the oven-status detection near
asks_about_the_oven to match “leave the oven” using separate
whitespace-delimited tokens rather than substring containment, preventing
matches within words such as “ovenware”; add a regression case covering that
false positive.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55e970f9-75bf-45b8-95aa-349e7be513d0

📥 Commits

Reviewing files that changed from the base of the PR and between 9e54db4 and f0dab0f.

📒 Files selected for processing (1)
  • crates/genie-core/src/tools/quick.rs

Comment thread crates/genie-core/src/tools/quick.rs Outdated
Comment thread crates/genie-core/src/tools/quick.rs Outdated
…ject

Review follow-up. The first pass gated on looks_like_status_query, which
still admitted a command embedded in a question — "what happens if i turn
the oven on", "check if i should turn the oven on" — and left "leave the
oven" matching inside "leave the ovenware".

Admit only the two shapes that ask about the oven's state, every token
matched whole: "is/are the oven ... on" with the oven as the subject (so
"are you going to turn the oven on" no longer qualifies) and "did ...
leave the oven ...". The generic gate is dropped entirely.

@matedev01 matedev01 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM — the oven branch had no question-shape gate and no word boundary, so 'turn the oven on' (a command) was answered with a status read and the oven never actuated — every other device already abstains on this word order, which made the oven the odd one out. Verified both CodeRabbit findings are genuinely fixed, not dismissed: asks_oven_state requires 'is'/'are' as the literal first word (rejects embedded/hypothetical forms like 'what happens if i turn the oven on'), and asks_oven_left_on whole-word-matches the 'leave the oven' triple (rejects 'ovenware'). Fixes #877. Verified: clippy -D warnings clean, quick-router tests (108) pass, fmt clean, live BFCL strict_accuracy 96.15% (no regression).

@matedev01
matedev01 merged commit a555f06 into GeniePod:main Jul 29, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] quick-router: "turn the oven on" is answered with a status read — the oven never actuates

2 participants