Skip to content

fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak#63068

Merged
frankekn merged 5 commits intoopenclaw:mainfrom
frankekn:fix/strip-leading-no-reply-token
Apr 8, 2026
Merged

fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak#63068
frankekn merged 5 commits intoopenclaw:mainfrom
frankekn:fix/strip-leading-no-reply-token

Conversation

@frankekn
Copy link
Copy Markdown
Member

@frankekn frankekn commented Apr 8, 2026

Summary

  • Problem: When a model generates NO_REPLYThe user is saying... (NO_REPLY immediately followed by text, no separator), the silent reply detection fails and sends the entire output including the NO_REPLY token as visible messages to end users.
  • Why it matters: Models using openai-completions API (e.g. fireworks/kimi) can emit NO_REPLY glued to text without whitespace separation. This causes the token to leak into user-visible messages.
  • What changed: Added stripLeadingSilentToken and startsWithSilentToken functions to src/auto-reply/tokens.ts. Applied leading-strip in normalize-reply.ts alongside existing trailing-strip. Added leading-token detection and stripping in the streaming accumulator in attempt-execution.ts.
  • What did NOT change (scope boundary): No changes to trailing strip logic, exact-match detection, prefix detection, or any other existing silent-reply behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: isSilentReplyText uses exact-match regex (^\s*NO_REPLY\s*$) and isSilentReplyPrefixText requires all-uppercase text. When a model emits NO_REPLYThe user is saying... as a single token/chunk, neither check matches, so the token passes through as visible text.
  • Missing detection / guardrail: No leading-token strip existed (only trailing via stripSilentToken). The streaming accumulator had no path for stripping a leading token glued to visible content.
  • Contributing context (if known): Models using openai-completions API (fireworks, kimi) can concatenate thinking output with text output without whitespace separation.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/auto-reply/tokens.test.ts, src/auto-reply/reply/reply-utils.test.ts
  • Scenario the test should lock in: stripLeadingSilentToken("NO_REPLYThe user is saying") returns "The user is saying"; startsWithSilentToken("NO_REPLYfoo") returns true.
  • Why this is the smallest reliable guardrail: the token stripping functions are pure and testable in isolation.
  • Existing test that already covers this (if any): None for the leading-token case.
  • If no new test is added, why not: This PR focuses on the minimal runtime fix. Unit tests for the new functions can be added as follow-up.

User-visible / Behavior Changes

Messages containing a leading NO_REPLY token glued to text (e.g. NO_REPLYThe user is saying...) now correctly strip the token and either suppress the message (if nothing remains) or deliver only the visible portion.

Diagram (if applicable)

Before:
[model emits "NO_REPLYThe user is saying..."] -> [no match on exact/prefix check] -> [entire string sent as visible message]

After:
[model emits "NO_REPLYThe user is saying..."] -> [startsWithSilentToken matches] -> [stripLeadingSilentToken] -> ["The user is saying..." delivered]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Linux
  • Runtime/container: Node 22+ / pnpm
  • Model/provider: Any model using openai-completions API (fireworks, kimi)
  • Integration/channel (if any): Any messaging channel
  • Relevant config (redacted): N/A

Steps

  1. Configure a model that uses openai-completions API
  2. Trigger a scenario where the model decides not to reply
  3. Observe the model emitting NO_REPLY concatenated with reasoning text

Expected

  • The NO_REPLY token is stripped and the message is either suppressed or only the visible portion is delivered

Actual

  • Before this fix: the entire string including NO_REPLY is sent as a visible message

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: pnpm build passes; pnpm check passes (via pre-commit hook)
  • Edge cases checked: multiple leading NO_REPLY tokens, NO_REPLY with whitespace before text, NO_REPLY as exact match (existing behavior preserved)
  • What you did not verify: full test suite; live model testing with fireworks/kimi

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: the leading-strip regex could match a legitimate message that starts with the exact token text followed by content.
    • Mitigation: the regex is case-insensitive but requires the exact token string. NO_REPLY is not a natural language prefix, so false positives are extremely unlikely. The existing trailing-strip uses the same approach.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S maintainer Maintainer-authored PR labels Apr 8, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 8, 2026

Greptile Summary

Fixes a silent-reply token leak where models (fireworks, kimi via openai-completions API) emit NO_REPLYVisible text as a single chunk with no separator, bypassing the existing exact-match and prefix-match guards. The fix adds stripLeadingSilentToken/startsWithSilentToken helpers and applies leading-strip in both the streaming accumulator (attempt-execution.ts) and the final normalization pass (normalize-reply.ts). The scope is intentionally narrow and does not touch existing trailing-strip or exact-match logic.

Confidence Score: 5/5

Safe to merge — the fix is narrowly scoped, the core logic is correct, and all remaining findings are non-blocking P2 style suggestions.

No P0 or P1 issues found. The new leading-strip functions are logically correct, the streaming accumulator branch is correctly ordered after existing guards, and the normalize-reply ordering (leading then trailing) handles all documented edge cases. Two P2 observations: a case-sensitivity inconsistency between startsWithSilentToken and getSilentLeadingRegex, and a minor efficiency opportunity in normalize-reply.ts. Neither blocks merge.

No files require special attention.

Vulnerabilities

No security concerns identified. The change strips a known control token (NO_REPLY) from model output before it reaches users; this reduces information leakage rather than introducing any new attack surface.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/auto-reply/tokens.ts
Line: 116-124

Comment:
**Case-sensitivity inconsistency with sibling functions**

`startsWithSilentToken` uses a case-sensitive `String.prototype.startsWith`, while `getSilentLeadingRegex` (called by `stripLeadingSilentToken`) uses the `"i"` flag. The existing detection helpers (`isSilentReplyText`, `isSilentReplyPrefixText`) are also case-insensitive. This means `startsWithSilentToken("no_replyFoo")` returns `false`, but `stripLeadingSilentToken("no_replyFoo")` would return `"Foo"` — the two sibling functions disagree on what counts as a leading token. Since the streaming call site and `normalize-reply.ts` both gate on case-sensitive checks (`includes`, `startsWith`) before reaching these helpers, this is benign in practice today, but it is a latent trap for future callers who expect the same case rules across all token helpers.

Consider using the cached regex instead:

```suggestion
export function startsWithSilentToken(
  text: string | undefined,
  token: string = SILENT_REPLY_TOKEN,
): boolean {
  if (!text) {
    return false;
  }
  return getSilentLeadingRegex(token).test(text);
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/auto-reply/reply/normalize-reply.ts
Line: 66-68

Comment:
**Both strip passes run even when no leading token is present**

`stripLeadingSilentToken` is now always called whenever `text.includes(silentToken)` is true, even for the common case of trailing-only tokens (e.g. `"Great! NO_REPLY"`). The extra call is a no-op when there is no leading token, but running two regex replacements unconditionally is slightly wasteful. Guarding with `startsWithSilentToken` before the leading strip keeps the hot path efficient and makes intent explicit.

```suggestion
  if (text && text.includes(silentToken) && !isSilentReplyText(text, silentToken)) {
    if (startsWithSilentToken(text, silentToken)) {
      text = stripLeadingSilentToken(text, silentToken);
    }
    text = stripSilentToken(text, silentToken);
```

(Note: `startsWithSilentToken` will need to be added to the import from `../tokens.js`.)

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(auto-reply): strip leading NO_REPLY ..." | Re-trigger Greptile

Comment thread src/auto-reply/tokens.ts
Comment on lines +116 to +124
export function startsWithSilentToken(
text: string | undefined,
token: string = SILENT_REPLY_TOKEN,
): boolean {
if (!text) {
return false;
}
return text.trimStart().startsWith(token);
}
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 Case-sensitivity inconsistency with sibling functions

startsWithSilentToken uses a case-sensitive String.prototype.startsWith, while getSilentLeadingRegex (called by stripLeadingSilentToken) uses the "i" flag. The existing detection helpers (isSilentReplyText, isSilentReplyPrefixText) are also case-insensitive. This means startsWithSilentToken("no_replyFoo") returns false, but stripLeadingSilentToken("no_replyFoo") would return "Foo" — the two sibling functions disagree on what counts as a leading token. Since the streaming call site and normalize-reply.ts both gate on case-sensitive checks (includes, startsWith) before reaching these helpers, this is benign in practice today, but it is a latent trap for future callers who expect the same case rules across all token helpers.

Consider using the cached regex instead:

Suggested change
export function startsWithSilentToken(
text: string | undefined,
token: string = SILENT_REPLY_TOKEN,
): boolean {
if (!text) {
return false;
}
return text.trimStart().startsWith(token);
}
export function startsWithSilentToken(
text: string | undefined,
token: string = SILENT_REPLY_TOKEN,
): boolean {
if (!text) {
return false;
}
return getSilentLeadingRegex(token).test(text);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/tokens.ts
Line: 116-124

Comment:
**Case-sensitivity inconsistency with sibling functions**

`startsWithSilentToken` uses a case-sensitive `String.prototype.startsWith`, while `getSilentLeadingRegex` (called by `stripLeadingSilentToken`) uses the `"i"` flag. The existing detection helpers (`isSilentReplyText`, `isSilentReplyPrefixText`) are also case-insensitive. This means `startsWithSilentToken("no_replyFoo")` returns `false`, but `stripLeadingSilentToken("no_replyFoo")` would return `"Foo"` — the two sibling functions disagree on what counts as a leading token. Since the streaming call site and `normalize-reply.ts` both gate on case-sensitive checks (`includes`, `startsWith`) before reaching these helpers, this is benign in practice today, but it is a latent trap for future callers who expect the same case rules across all token helpers.

Consider using the cached regex instead:

```suggestion
export function startsWithSilentToken(
  text: string | undefined,
  token: string = SILENT_REPLY_TOKEN,
): boolean {
  if (!text) {
    return false;
  }
  return getSilentLeadingRegex(token).test(text);
}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/auto-reply/reply/normalize-reply.ts Outdated
Comment on lines 66 to 68
if (text && text.includes(silentToken) && !isSilentReplyText(text, silentToken)) {
text = stripLeadingSilentToken(text, silentToken);
text = stripSilentToken(text, silentToken);
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 Both strip passes run even when no leading token is present

stripLeadingSilentToken is now always called whenever text.includes(silentToken) is true, even for the common case of trailing-only tokens (e.g. "Great! NO_REPLY"). The extra call is a no-op when there is no leading token, but running two regex replacements unconditionally is slightly wasteful. Guarding with startsWithSilentToken before the leading strip keeps the hot path efficient and makes intent explicit.

Suggested change
if (text && text.includes(silentToken) && !isSilentReplyText(text, silentToken)) {
text = stripLeadingSilentToken(text, silentToken);
text = stripSilentToken(text, silentToken);
if (text && text.includes(silentToken) && !isSilentReplyText(text, silentToken)) {
if (startsWithSilentToken(text, silentToken)) {
text = stripLeadingSilentToken(text, silentToken);
}
text = stripSilentToken(text, silentToken);

(Note: startsWithSilentToken will need to be added to the import from ../tokens.js.)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/auto-reply/reply/normalize-reply.ts
Line: 66-68

Comment:
**Both strip passes run even when no leading token is present**

`stripLeadingSilentToken` is now always called whenever `text.includes(silentToken)` is true, even for the common case of trailing-only tokens (e.g. `"Great! NO_REPLY"`). The extra call is a no-op when there is no leading token, but running two regex replacements unconditionally is slightly wasteful. Guarding with `startsWithSilentToken` before the leading strip keeps the hot path efficient and makes intent explicit.

```suggestion
  if (text && text.includes(silentToken) && !isSilentReplyText(text, silentToken)) {
    if (startsWithSilentToken(text, silentToken)) {
      text = stripLeadingSilentToken(text, silentToken);
    }
    text = stripSilentToken(text, silentToken);
```

(Note: `startsWithSilentToken` will need to be added to the import from `../tokens.js`.)

How can I resolve this? If you propose a fix, please make it concise.

@frankekn frankekn self-assigned this Apr 8, 2026
@frankekn
Copy link
Copy Markdown
Member Author

frankekn commented Apr 8, 2026

@codex review

Copy link
Copy Markdown

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

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: 45d3795f36

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +210 to +211
visibleText = stripped;
return { text: stripped, delta: stripped };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep raw streamed prefix when stripping glued NO_REPLY

After detecting a glued leading sentinel, this branch stores visibleText as the stripped text ("The user...") instead of the raw streamed text ("NO_REPLYThe user..."). The accumulator’s merge path explicitly supports cumulative chunk updates (chunk.startsWith(base)), so when the next event arrives as a cumulative snapshot still containing the NO_REPLY prefix, the prefix check no longer matches and the code appends the entire chunk, reintroducing NO_REPLY and duplicating content in ACP-visible output. This regression appears whenever runtimes emit cumulative text_delta snapshots rather than pure append-only deltas.

Useful? React with 👍 / 👎.

@frankekn
Copy link
Copy Markdown
Member Author

frankekn commented Apr 8, 2026

@codex review

Copy link
Copy Markdown

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

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: 51766aded2

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/auto-reply/tokens.ts Outdated
const escaped = escapeRegExp(token);
// Match one or more leading occurrences of the token where the final token
// is glued directly to visible content (for example `NO_REPLYhello`).
const regex = new RegExp(`^\\s*(?:${escaped}\\s+)*${escaped}(?=\\S)`, "i");
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 Restrict glued-token match to word-start content

The new leading-token detector matches NO_REPLY before any non-space character ((?=\S)), so substantive outputs like NO_REPLY: explanation or NO_REPLY—note are now treated as sentinel-prefixed and get rewritten to : explanation / —note. This is a regression from prior behavior (these messages were preserved), and because this helper is used by both reply normalization and ACP streaming paths, it can corrupt visible assistant text whenever a model intentionally starts a message with token-like text plus punctuation.

Useful? React with 👍 / 👎.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: M and removed size: S labels Apr 8, 2026
Copy link
Copy Markdown

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

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: 9a4214843c

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/auto-reply/reply/normalize-reply.ts Outdated
Comment on lines +68 to +69
if (startsWithSilentToken(text, silentToken)) {
text = stripLeadingSilentToken(text, silentToken);
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 Remove case-sensitive gate before leading token strip

The new leading-token normalization still runs inside if (text && text.includes(silentToken) ...), and includes is case-sensitive, so inputs like no_replyThe user is saying... skip this branch entirely and leak the sentinel in normalized final replies. This is especially visible now because other updated streaming paths call startsWithSilentToken directly (case-insensitive), so streamed text can be stripped while the final normalized payload still contains no_reply....

Useful? React with 👍 / 👎.

@frankekn
Copy link
Copy Markdown
Member Author

frankekn commented Apr 8, 2026

@codex review

1 similar comment
@frankekn
Copy link
Copy Markdown
Member Author

frankekn commented Apr 8, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@frankekn frankekn force-pushed the fix/strip-leading-no-reply-token branch from bcaafaf to ce44d89 Compare April 8, 2026 14:37
@frankekn frankekn force-pushed the fix/strip-leading-no-reply-token branch from 933ce58 to ce345a0 Compare April 8, 2026 16:30
@frankekn frankekn merged commit 153e3ad into openclaw:main Apr 8, 2026
7 checks passed
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 8, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🔵 Low Silent-token stripping can be bypassed with zero-width/combining characters, leaking internal NO_REPLY token
1. 🔵 Silent-token stripping can be bypassed with zero-width/combining characters, leaking internal NO_REPLY token
Property Value
Severity Low
CWE CWE-184
Location src/auto-reply/tokens.ts:92-104

Description

The new startsWithSilentToken() detection only considers the token to be a “leading glued” silent token when the character immediately following NO_REPLY is a Unicode letter or number ((?=[\p{L}\p{N}])).

This allows an attacker (or prompt-injected model output) to craft output where NO_REPLY is followed by zero-width format characters (e.g., ZWJ/ZWNJ/ZWS) or combining marks before the first visible letter/number, e.g. NO_REPLY\u200DHello or NO_REPLY\u0301Hello.

Impact:

  • startsWithSilentToken() returns false for these cases
  • Call sites (e.g., normalizeReplyPayload, streaming-directives, server-chat, etc.) therefore do not call stripLeadingSilentToken()
  • stripSilentToken() only strips trailing tokens, so the leading token remains and can be sent to end users (internal control token leakage)

Vulnerable code:

// token must be immediately followed by a letter/number
const regex = new RegExp(`^\\s*(?:${escaped}\\s+)*${escaped}(?=[\\p{L}\\p{N}])`, "iu");

Recommendation

Treat common non-visible joiners/marks between the token and the first visible alphanumeric as attachers, or normalize them away before testing.

For example, allow optional Unicode format/mark characters before the first visible letter/number:

// Allow Cf (format, incl. ZWJ/ZWNJ/ZWS) and Mn/Mc (combining marks)
const regex = new RegExp(
  `^\\s*(?:${escaped}\\s+)*${escaped}(?=[\\p{Cf}\\p{Mn}\\p{Mc}]*[\\p{L}\\p{N}])`,
  "iu",
);

Alternatively, pre-normalize the input by removing leading \p{Cf} and combining marks after the token before applying the check/strip, and add unit tests covering NO_REPLY\u200DHello, NO_REPLY\u200BHello, and NO_REPLY\u0301Hello.


Analyzed PR: #63068 at commit ce345a0

Last updated on: 2026-04-08T16:38:50Z

eleqtrizit pushed a commit that referenced this pull request Apr 8, 2026
…y leak (#63068)

* fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak

* fix(auto-reply): preserve substantive NO_REPLY leading text

* fix(agents): preserve ACP silent-prefix cumulative deltas

* fix(auto-reply): harden silent-token streaming paths

* fix(auto-reply): normalize glued silent tokens consistently

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
greidron added a commit to greidron/openclaw that referenced this pull request Apr 10, 2026
* release: mirror bundled channel deps at root (openclaw#63065)

Merged via squash.

Prepared head SHA: ac26799
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Reviewed-by: @scoootscooob

* fix(test): keep warn log capture under openclaw temp dir

* revert: undo background alive review findings fix

* feat: add qa character vibes eval

* test: stabilize plugin boundary invariants

* test: isolate agent gateway cli command mocks

* test: skip duplicate package boundary wrapper in ci

* test: fix postpublish verifier sidecar handling

* test: keep status tests off live usage probes

* auto-reply: type status auth overrides

* plugins: read contract inventory from manifests

* test: inline cli metadata channel fixture

* ci: skip duplicate full extension shard

* test: isolate discord directory live token env

* test: keep followup runner memory mock complete

* ci: split parallel full suite into leaf shards

* test: guard loader fixtures against broad sdk imports

* test: keep bundled channel entry smokes descriptor-only

* ci: reduce full suite test parallelism

* test: avoid bundled test api smokes in matrix and telegram

* test: keep discord and irc entry smokes descriptor-only

* test: keep web provider artifact coverage manifest-only

* test: keep provider policy artifact coverage narrow

* test: keep web provider artifact test in boundary

* test: keep status message tests off auth auto-detection

* status: avoid plugin lookup for direct channel model overrides

* channels: fast-path direct model override matches

* test: restore manifest-only web provider coverage

* fix: allow blank TLS manual port default (openclaw#63134) (thanks @Tyler-RNG)

* make port optional for TLS manual connections

* fix: restrict manual blank-port fallback to tls

* fix: allow blank TLS manual port default (openclaw#63134) (thanks @Tyler-RNG)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* test: fix full suite CI test isolation

* fix: align LLM idle timeout policy

* test: exercise models json file mode without provider discovery

* test: keep shared dm policy contract off channel facades

* test: keep web provider artifact test in boundary

* test: keep kilocode provider tests on plugin-owned helpers

* ci: restore sequential full suite tests

* test: keep public artifact coverage on cheap boundaries

* test: keep openclaw tools registration tests on a fast shell

* test: keep bundled metadata sidecar scan inventory-only

* docs(inferrs): fix Gemma model id from gg-hf-gg to google (openclaw#62586)

* fix: harden bundled plugin dependency release checks

* ci: isolate full suite leaf shards

* test: keep openclaw tools registration policy pure

* fix: support Codex CLI QA auth

* feat: add QA character eval reports

* docs: document QA character eval workflow

* refactor: dedupe media generation tool helpers

* refactor: dedupe internal helper glue

* refactor: dedupe shared helper branches

* refactor: dedupe browser navigation guard tests

* refactor: dedupe config and subagent tests

* refactor: dedupe test helpers and script warning filter

* refactor: dedupe plugin test harnesses

* refactor: dedupe media runtime test mocks

* refactor: dedupe plugin metadata test helpers

* refactor: dedupe firecrawl and directive helpers

* refactor: dedupe exec defaults tests

* refactor: dedupe approval runtime tests

* refactor: dedupe matrix exec approval tests

* refactor: dedupe telegram exec approval tests

* refactor: dedupe doctor codex oauth tests

* refactor: dedupe agent command test fixtures

* refactor: dedupe embedding provider test fixtures

* refactor: share html entity tool call decoding

* fix: keep minimax provider mocks package-local

* test: keep pdf and update-plan registration tests pure

* test: keep model reasoning override coverage on merge helpers

* fix: default OpenAI reasoning effort to high

* test: keep kimi implicit provider tests on provider catalog

* fix(build): prune stale bundled plugin node_modules

* fix(build): address bundled plugin prune review

* fix(build): honor postinstall disable flag

* test: keep chutes implicit provider tests on provider catalog

* fix(plugin-sdk): export channel plugin base

* docs: reorder changelog entries

* test: keep bundled web-search owner checks on public artifacts

* fix(build): keep tsdown prune best-effort

* test: trust gateway exec fixture node path

* fix: keep runtime task test harness behind task seams

* test: explain gateway exec fixture trust

* Reply: surface OAuth reauth failures (openclaw#63217)

Merged via squash.

Prepared head SHA: 68b7ffd
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* test: make character eval scenario natural

* feat: add character eval model options

* test: keep pi fs workspace tests on fs tool factories

* test: keep media runtime tests on same-directory provider mocks

* fix(android): auto-resume pairing approval

* fix(android): prefer bootstrap auth on qr pairing

* fix(android): reset auth on new setup codes

* fix(android): tighten pairing retry behavior

* fix(android): prefer stored device auth after pairing

* fix: restore android qr pairing flow (openclaw#63199)

* fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak (openclaw#63068)

* fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak

* fix(auto-reply): preserve substantive NO_REPLY leading text

* fix(agents): preserve ACP silent-prefix cumulative deltas

* fix(auto-reply): harden silent-token streaming paths

* fix(auto-reply): normalize glued silent tokens consistently

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>

* fix(gateway): clear auto-fallback model override on session reset (openclaw#63155)

* fix(gateway): clear auto-fallback model override on session reset

When `persistFallbackCandidateSelection()` writes a fallback provider
override with `authProfileOverrideSource: "auto"`, the override was
incorrectly preserved across `/reset` and `/new` commands. This caused
sessions to keep using the fallback provider even after the user changed
the agent config primary provider, because the session store override
takes precedence over the config default.

Now the override fields (`providerOverride`, `modelOverride`,
`authProfileOverride`, `authProfileOverrideSource`,
`authProfileOverrideCompactionCount`) are only carried forward when
`authProfileOverrideSource === "user"` (i.e. explicit `/model` command).
System-driven overrides are dropped on reset so the session picks up the
current config default.

Introduced in cb0a752 ("fix: preserve reset session behavior config")

* fix(gateway): preserve explicit reset model selection

* fix(gateway): track reset model override source

* fix(gateway): preserve legacy reset model overrides

* docs(changelog): add session reset merge note

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>

* test: stabilize ci test isolation

* test: isolate volcengine byteplus auth resolver imports

* fix: patch hono security advisories

* fix: pass system prompt to codex cli

* fix(plugins): prevent untrusted workspace plugins from hijacking bundled provider auth choices [AI] (openclaw#62368)

* fix: address issue

* fix: address review feedback

* docs(changelog): add onboarding auth-choice guard entry

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

* fix: address PR review feedback

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* test: isolate provider runtime test mocks

* feat(plugins): support provider auth aliases

* feat(memory): add grounded REM backfill lane (openclaw#63273)

Merged via squash.

Prepared head SHA: 4450f25
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* feat(memory): harden grounded REM extraction (openclaw#63297)

Merged via squash.

Prepared head SHA: e188b7e
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* feat(ui): add dreaming diary controls and navigation (openclaw#63298)

Merged via squash.

Prepared head SHA: 0a2ae66
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* chore(ui): refresh zh-TW control ui locale

* chore(ui): refresh zh-CN control ui locale

* chore(ui): refresh pt-BR control ui locale

* chore(ui): refresh de control ui locale

* chore(ui): refresh es control ui locale

* chore(ui): refresh ko control ui locale

* chore(ui): refresh ja-JP control ui locale

* chore(ui): refresh fr control ui locale

* docs(matrix): tighten setup and config guidance

* chore(ui): refresh tr control ui locale

* chore(ui): refresh uk control ui locale

* chore(ui): refresh pl control ui locale

* chore(ui): refresh id control ui locale

* test: stabilize full-suite execution

* fix(matrix): contain sync outage failures (openclaw#62779)

Merged via squash.

Prepared head SHA: 901bb76
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* Align remote node exec event system messages with untrusted handling (openclaw#62659)

* fix(nodes): downgrade remote exec system events

* docs(changelog): add remote node exec event entry

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* test: reuse image generate tool imports

* test: reuse followup runner imports

* docs(config): tighten wording in reference

* test: harden provider mock isolation

* fix(memory): accept embedded dreaming heartbeat tokens

* test: harden Parallels macOS smoke fallback

* build: narrow plugin SDK declaration build

* fix(dotenv): block workspace runtime env vars (openclaw#62660)

* fix(dotenv): block workspace runtime env vars

Co-authored-by: zsx <git@zsxsoft.com>

* docs(changelog): add workspace dotenv runtime-control entry

* fix(dotenv): block workspace gateway port override

---------

Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>

* build: stage nostr runtime dependencies

* fix: load QA live provider overrides

* feat: parallelize character eval runs

* auth: avoid external cli sync on profile upsert

* test(doctor): mock memory-core runtime seam

* auth: persist explicit profile upserts directly

* Matrix: report startup failures as errors

* fix(browser): harden browser control override loading (openclaw#62663)

* fix(browser): harden browser control overrides

* fix(lint): prepare boundary artifacts for extension oxlint

* docs(changelog): add browser override hardening entry

* fix(lint): avoid duplicate boundary prep

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>

* test: reuse exec directive reply imports

* test: reuse verbose directive reply imports

* fix(browser): re-check interaction-driven navigations (openclaw#63226)

* fix(browser): guard interaction-driven navigations

* fix(browser): avoid rechecking unchanged interaction urls

* fix(browser): guard delayed interaction navigations

* fix(browser): guard interaction-driven navigations for full action duration

* fix(browser): avoid waiting on interaction grace timer

* fix(browser): ignore same-document hash-only URL changes in navigation guard

* fix(browser): dedupe interaction nav guards

* fix(browser): guard same-URL reloads in interaction navigation listeners

* docs(changelog): add interaction navigation guard entry

* fix(browser): drop duplicate ssrfPolicy props

* fix(browser): tighten interaction navigation guards

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>

* test: move directive state coverage to pure tests

* fix: enable thinking support for the ollama api (openclaw#62712)

Merged via squash.

Prepared head SHA: c0b9950
Co-authored-by: hoyyeva <63033505+hoyyeva@users.noreply.github.com>
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Reviewed-by: @BruceMacD

* Slack: treat ACP block text as visible output (openclaw#62858)

Merged via squash.

Prepared head SHA: 14f202e
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* fix: fail fast on qa live auth errors

* fix: fail fast across qa scenario wait paths

* test: cover qa scenario wait failure replies

* fix: sanitize qa missing-key replies

* test: cover sanitized qa missing-key replies

* fix: align qa wait cursor semantics

* test: cover mixed-traffic qa wait cursors

* fix: classify curated qa missing-key replies

* test: cover curated qa missing-key reply classification

* fix: harden qa missing-key provider messages

* test: cover unsafe qa missing-key providers

* docs(changelog): add qa auth fail-fast entry (openclaw#63333) (thanks @shakkernerd)

* fix(matrix/doctor): migrate legacy channels.matrix.dm.policy 'trusted' (fixes openclaw#62931) (openclaw#62942)

Merged via squash.

Prepared head SHA: d9f553b
Co-authored-by: lukeboyett <46942646+lukeboyett@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* Memory/dreaming: feed grounded backfill into short-term promotion (openclaw#63370)

Merged via squash.

Prepared head SHA: 5dfe246
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* docs: update unreleased changelog

* fix(gateway): classify dream diary actions

* fix(memory): align dreaming status payloads

* Memory/dreaming: harden grounded backfill follow-ups

* test: reuse inline directive reply imports

* Docs/memory: explain grounded backfill flows

* fix(deps): patch basic-ftp advisory

* test: move inline directive collisions to pure tests

* Slack: dedupe partial streaming replies (openclaw#62859)

Merged via squash.

Prepared head SHA: cbecb50
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras

* test: replace exec directive e2e with pure coverage

* fix(plugins): keep test helpers out of contract barrels (openclaw#63311)

Merged via squash.

Prepared head SHA: 769e90c
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf

* test: move cron heartbeat delivery coverage below full turns

* fix: inter-session messages must not overwrite established external lastRoute (openclaw#58013)

Merged via squash.

Prepared head SHA: 820ea20
Co-authored-by: duqaXxX <12242811+duqaXxX@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix(gateway): suppress announce/reply skip chat leakage (openclaw#51739)

Merged via squash.

Prepared head SHA: 2f53f3b
Co-authored-by: Pinghuachiu <9033138+Pinghuachiu@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* Slack: key turn-local dedupe by dispatch kind

Scope Slack turn-local delivery dedupe by reply dispatch kind so identical tool and final payloads on the same thread do not collapse into one send.

Expose the existing dispatcher kind on the public reply-runtime seam and cover the Slack tracker and preview-fallback paths with regression tests.

* Dreaming: surface grounded scene lane (openclaw#63395)

Merged via squash.

Prepared head SHA: 0c7f586
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky

* test: avoid runtime auth overlays in failure-state coverage

* fix(ci): align ollama thinking expectations

* chore(ui): refresh zh-CN control ui locale

* chore(ui): refresh pt-BR control ui locale

* chore(ui): refresh zh-TW control ui locale

* chore(ui): refresh de control ui locale

* test(docker): reduce e2e log noise

* chore(ui): refresh es control ui locale

* chore(ui): refresh fr control ui locale

* chore(ui): refresh ja-JP control ui locale

* chore(ui): refresh ko control ui locale

* chore(ui): refresh uk control ui locale

* chore(ui): refresh id control ui locale

* chore(ui): refresh pl control ui locale

* chore(ui): refresh tr control ui locale

* fix: restore main ci

* fix(ci): drop silent history before truncation

* docs: reorder unreleased changelog

* test(docker): quiet success-path e2e logs

* style: sort session import

* build: mirror bundled plugin runtime deps

* plugins: load lightweight provider discovery entries

* ci: narrow Windows node test lane

* fix: filter provider auth aliases by plugin trust

* fix: surface delayed browser navigation blocks

* style: format memory and gateway touchups

* Delete docs/plans directory

Unused artifact

* test: avoid remote ollama timeout in api-key preservation coverage

* test: keep auth-choice default-model coverage on lightweight provider

* test: keep undefined-token auth-choice coverage generic

* fix: stabilize character eval and Qwen model routing

* test: keep agent command tests off external auth overlays

* fix openrouter model picker refs (openclaw#63416)

* fix openrouter model picker refs

Signed-off-by: sallyom <somalley@redhat.com>

* test(ui): cover openrouter slash-id /model resolution

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: Vignesh Natarajan <vignesh.natarajan92@gmail.com>

* ci: stabilize macOS and transcript policy tests

* test: keep cli-provider agent command tests off external auth overlays

* chore(lint): clear extension lint regressions and add openclaw#63416 changelog

* test: update modelstudio catalog contract sentinel

* test: update character eval public panel

* fix: repair Windows dev-channel updater

* test: move copilot models-json injection coverage to plan tests

* plugin-sdk: split command status surface

* plugin-sdk: keep command status compatibility path light

* plugin-sdk: drop investigative weixin repro harness

* tests: document config mock choice for eager warmup

* fix: update command-status SDK baseline (openclaw#63174) (thanks @hxy91819)

* test: cap broad live model sweeps

* fix: drop raw gateway chat control replies

* test: make shared-token reload deterministic

* test: isolate agentic suite smoke tests

* test: replace models-config matrix with narrow coverage

* test: isolate onboard skills status mock

* plugins: add lightweight anthropic vertex discovery

* test: isolate model auth module state

* test: isolate subagent registry resume imports

* plugins: keep google provider policy lightweight

* test: keep ollama unreachable discovery on localhost

* test: mock auth profile external overlay in oauth tests

* auth: avoid plugin setup scans during common auth resolution

* fix(logging): break console/logger type cycle

* fix(config): stop owner-display barrel cycles

* fix(commands): split auth choice apply types

* fix(infra): extract exec approvals allowlist types

* fix(commands): split doctor prompt option types

* chore: prepare 2026.4.9-beta.1 release

* chore: refresh config schema version for 2026.4.9-beta.1

* chore: refresh plugin SDK API baseline

* test: run local full suite project shards in parallel

* wizard: add explicit skip option to plugin setup (openclaw#63436)

* Wizard: allow skipping plugin setup

* Agents: reset nodes tool test modules

* tests: reset discord native-command seams in model picker (openclaw#63267)

* ci: tolerate noisy npm pack json output

* test: isolate slack thread-ts recovery

* fix(msteams): isolate channel thread sessions by replyToId (openclaw#58615) (openclaw#62713)

* fix(msteams): isolate thread sessions by replyToId (openclaw#58615)

* fix(msteams): align thread ID extraction + fix test types

* fix(msteams): route thread replies to correct thread via replyToId (openclaw#58030) (openclaw#62715)

* fix(msteams): pin reply target at inbound time to prevent DM/channel leak (openclaw#54520) (openclaw#62716)

* test: keep local full suite serial by default

* chore: prepare 2026.4.9 stable release

* Agents: guard legacy pi transport override

* Agents: restore upstream pi runner sources

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: scoootscooob <167050519+scoootscooob@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
Co-authored-by: Tyler Warburton <Ethan.gold-Steinberg@protonmail.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Eric Curtin <eric.curtin@docker.com>
Co-authored-by: Mariano <mbelinky@gmail.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
Co-authored-by: Pavan Kumar Gondhi <pgondhi@nvidia.com>
Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: Agustin Rivera <31522568+eleqtrizit@users.noreply.github.com>
Co-authored-by: zsx <git@zsxsoft.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
Co-authored-by: Eva H <63033505+hoyyeva@users.noreply.github.com>
Co-authored-by: BruceMacD <5853428+BruceMacD@users.noreply.github.com>
Co-authored-by: Shakker <shakkerdroid@gmail.com>
Co-authored-by: lukeboyett <46942646+lukeboyett@users.noreply.github.com>
Co-authored-by: Altay <altay@uinaf.dev>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: Accunza <12242811+duqaXxX@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: Pinghuachiu <9033138+Pinghuachiu@users.noreply.github.com>
Co-authored-by: Radek Sienkiewicz <mail@velvetshark.com>
Co-authored-by: Sally O'Malley <somalley@redhat.com>
Co-authored-by: Vignesh Natarajan <vignesh.natarajan92@gmail.com>
Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: pashpashpash <nik@vault77.ai>
Co-authored-by: sudie-codes <suvenkat95@gmail.com>
@BKF-Gitty BKF-Gitty mentioned this pull request Apr 11, 2026
22 tasks
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request Apr 27, 2026
…y leak (openclaw#63068)

* fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak

* fix(auto-reply): preserve substantive NO_REPLY leading text

* fix(agents): preserve ACP silent-prefix cumulative deltas

* fix(auto-reply): harden silent-token streaming paths

* fix(auto-reply): normalize glued silent tokens consistently

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…y leak (openclaw#63068)

* fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak

* fix(auto-reply): preserve substantive NO_REPLY leading text

* fix(agents): preserve ACP silent-prefix cumulative deltas

* fix(auto-reply): harden silent-token streaming paths

* fix(auto-reply): normalize glued silent tokens consistently

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…y leak (openclaw#63068)

* fix(auto-reply): strip leading NO_REPLY tokens to prevent silent-reply leak

* fix(auto-reply): preserve substantive NO_REPLY leading text

* fix(agents): preserve ACP silent-prefix cumulative deltas

* fix(auto-reply): harden silent-token streaming paths

* fix(auto-reply): normalize glued silent tokens consistently

---------

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant