Skip to content

fix(goal): suppress reserved SILENT sentinel at /api/goal ingress - #7046

Closed
webtecnica wants to merge 1 commit into
nesquena:masterfrom
webtecnica:fix/7019-goal-silent-ingress
Closed

webtecnica wants to merge 1 commit into
nesquena:masterfrom
webtecnica:fix/7019-goal-silent-ingress

Conversation

@webtecnica

Copy link
Copy Markdown
Contributor

Problem

/api/goal allows the exact reserved [SILENT] suppression sentinel to reach _start_chat_stream_for_session, which persists it as pending_user_message. If 8701 restarts while that turn is pending, session recovery materializes it as a visible _recovered user turn — the same phantom-recovered-turn exposure #7018 closed for the /api/chat/start and start_session_turn ingress paths, but via a different route.

Fix

Apply the same _is_silent_control_message() guard (single source of truth, introduced in #7018) immediately after /api/goal session-ID validation and before session lookup (_session_is_subagent_view_only / get_session) or any goal-state mutation. It returns the same 200 no-op:

{"status": "suppressed", "reason": "silent_control_message"}

Matching is deliberately exact and case-sensitive (whitespace-normalized): only [SILENT] is suppressed; [silent], prose containing [SILENT], and ordinary kickoff text are untouched.

Verification

Added tests/test_goal_silent_ingress_suppression.py mirroring tests/test_silent_control_suppression.py for the goal path:

  1. /api/goal with args="[SILENT]" returns 200 suppressed without session lookup.
  2. Same suppression via the text field (goal accepts both).
  3. Ordinary kickoff text is not suppressed (request proceeds to session lookup).
  4. Exact-match / case-sensitivity semantics of the shared helper.

Results: 7 passed (new file + existing test_silent_control_suppression.py) and 58 passed across the goal/runtime-adapter suites (test_goal_command_webui.py, test_runtime_adapter_seam.py, test_issue_1932_goal_hook_unrelated_turns.py).

Closes #7019

A wake relay POSTing the exact [SILENT] suppression sentinel to
/api/goal lets it reach _start_chat_stream_for_session, which persists
it as pending_user_message. If 8701 restarts while the turn is pending,
session recovery materializes it as a visible _recovered user turn —
the same phantom-recovered-turn exposure nesquena#7018 closed for chat ingress.

Apply the shared _is_silent_control_message() guard immediately after
/api/goal session-ID validation and before session lookup or goal-state
mutation, returning the same 200 no-op. Matching stays exact and
case-sensitive; ordinary kickoff text is unaffected.

Add tests mirroring test_silent_control_suppression.py for the goal
path (args and text fields, before-lookup suppression, exact-match
semantics).

Closes nesquena#7019
@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR suppresses the exact whitespace-normalized [SILENT] sentinel at /api/goal before session lookup or goal-state mutation.

  • Adds an early successful no-op response in api/routes.py.
  • Adds focused tests for both accepted input fields, ordinary kickoff behavior, and exact case-sensitive matching.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking documentation update needed for the new /api/goal suppression contract.

The guard consistently uses the existing sentinel helper and is covered by focused regression tests, but the changed endpoint response and validation ordering are not documented in the repository.

Files Needing Attention: api/routes.py

Important Files Changed

Filename Overview
api/routes.py Adds the intended early sentinel guard; the runtime contract change lacks the repository-required documentation update.
tests/test_goal_silent_ingress_suppression.py Covers suppression through both fields, pre-lookup ordering, ordinary input, and exact case-sensitive helper semantics.

Reviews (1): Last reviewed commit: "fix(goal): suppress reserved SILENT sent..." | Re-trigger Greptile

Comment thread api/routes.py
Comment on lines +22540 to +22545
if _is_silent_control_message(body.get("args") or body.get("text")):
return j(
handler,
{"status": "suppressed", "reason": "silent_control_message"},
status=200,
)

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.

P2 Document the suppression contract

The new early return changes /api/goal into a successful no-op for the exact [SILENT] sentinel before session lookup, but repository documentation does not describe this response, its case-sensitive matching semantics, or its validation ordering.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

🔬 Exact-head gate certification — SAFE TO SHIP (queued for maintainer merge)

Gated at head a430dd4dbcea (0 behind master). This is a faithful, minimal extension of the #7018 phantom-recovered-turn fix to the one ingress path it missed.

Root cause confirmed against source: _is_silent_control_message() guards /api/chat/start (routes.py:22181) and start_session_turn (routes.py:22702), but _handle_goal_command (routes.py:22537) had no such guard — so a [SILENT] sentinel posted to /api/goal reaches _start_chat_stream_for_session and persists as pending_user_message, which session recovery later materializes as a visible _recovered turn. The +6-line guard closes exactly that gap.

Field correctness: the guard checks body.get("args") or body.get("text") — the identical precedence the downstream goal message uses (goal_args = str(body.get("args","") or body.get("text","") or ""), line ~22591), so there's no false-negative window.

Gate results (this exact head):

  • Codex adversarial gate: SAFE TO SHIP — verified (1) suppresses only the exact whitespace-trimmed [SILENT] (no false-positive on legit /goal status|pause|resume|clear|stop|done), (2) the early return fires before session lookup / profile change / goal mutation / pending-message persistence (no side-effect skipped), (3) /api/chat/start + start_session_turn already share the helper so no sibling stream ingress remains exposed — this is the last gap, (4) client (commands.js:1259) safely no-ops on a stream_id-less suppressed response.
  • Full suite: 14,583 passed, 0 failures.
  • CI: 23/23 green. mergeable_state: clean.
  • PR's own test (test_goal_silent_ingress_suppression.py): 4/4.

Tight, additive, pure-backend, near-zero regression risk. Queued gate-pass for a maintainer merge nod. Thanks @webtecnica.

(Release-manager overnight gate — queued for maintainer merge, not autonomously tagged.)

@nesquena-hermes nesquena-hermes added gate-pass Full gate passed (Codex+Opus+suite+browser); queued Tier 1 for release agent merge-soon size:M Medium PR (≤10 files, ≤250 LOC) labels Aug 15, 2026
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Shipped in experimental release exp-v0.52.229 (batched with 5 other low-risk gate-passed fixes). Merged onto master via the batch release PR #7089 — your commit content is in master and is live on the experimental channel now.

Thanks @webtecnica for the fix (#7019).

🤖 Closed by the release-manager agent.

@webtecnica

Copy link
Copy Markdown
Contributor Author

Thank you for shipping this in exp-v0.52.229! 🚀 Glad the fix landed on master via the batch release #7089. Appreciate the kind words!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gate-pass Full gate passed (Codex+Opus+suite+browser); queued Tier 1 for release agent merge-soon size:M Medium PR (≤10 files, ≤250 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suppress reserved [SILENT] sentinel at the /api/goal ingress path too (class-completeness follow-up to #7018)

2 participants