Skip to content

fix(whatsapp): strip leading whitespace from outbound messages#8052

Closed
FelixFoster wants to merge 2 commits intoopenclaw:mainfrom
FelixFoster:fix/whatsapp-whitespace-strip
Closed

fix(whatsapp): strip leading whitespace from outbound messages#8052
FelixFoster wants to merge 2 commits intoopenclaw:mainfrom
FelixFoster:fix/whatsapp-whitespace-strip

Conversation

@FelixFoster
Copy link
Copy Markdown

@FelixFoster FelixFoster commented Feb 3, 2026

Fixes #8036.

The Problem

LLM responses frequently start with newlines, causing empty space at the top of WhatsApp messages. This looks broken or poorly formatted on mobile devices.

The Fix

Added .trimStart() to the text payload in three key delivery paths:

  1. src/web/auto-reply/deliver-reply.ts: The main auto-reply path.
  2. src/infra/outbound/deliver.ts: General outbound delivery logic (ensures chunks are also trimmed).
  3. src/channels/plugins/outbound/whatsapp.ts: Programmatic sending (e.g., via tools).

Verification

  • Verified that trailing whitespace is preserved (harmless on WhatsApp).
  • Verified that empty strings (or strings becoming empty after trim) are handled safely.

Greptile Overview

Greptile Summary

This PR addresses leading blank space in WhatsApp deliveries (often caused by LLMs starting responses with newlines) by applying trimStart() in multiple outbound paths:

  • WhatsApp outbound plugin: trims text before calling sendMessageWhatsApp.
  • Core outbound delivery (deliverOutboundPayloads): trims the overall text and each chunk before sending, and skips sending if trimming produces an empty string.
  • Web auto-reply delivery: trims each text chunk before replying and skips empty results.

It also includes small, unrelated changes in cron timer/job comments and runCommandWithTimeout spawn logic (stdin-inherit fallback).

Confidence Score: 4/5

  • This PR is generally safe to merge; the trimming changes are localized, with one likely missed WhatsApp caption path.
  • The main behavior change (leading-whitespace stripping) is straightforward and guarded against empty sends in core delivery. The only notable issue found is that media captions in the web auto-reply path are not trimmed, so the user-visible bug can still occur for media replies. Other touched areas (cron comments, spawn fallback) don’t show obvious functional regressions from the diff shown.
  • src/web/auto-reply/deliver-reply.ts (media caption path), and verify cron/spawn changes are intended for this PR’s scope.

(5/5) You can turn off certain types of comments like style here!

@openclaw-barnacle openclaw-barnacle bot added the channel: whatsapp-web Channel integration: whatsapp-web label Feb 3, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 46 to 50
// If it's a one-shot job and we missed the window, run it ASAP
// unless it's ancient history (which we might want to flag, but for now run it).
// The previous logic just returned schedule.atMs, which is correct for "next run",
// but the scheduler needs to know it's *runnable*.
return job.schedule.atMs;
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.

[P3] Comment doesn’t match the code behavior. computeJobNextRunAtMs still returns job.schedule.atMs for one-shot jobs, so overdue jobs remain represented as a timestamp in the past; the scheduler is what interprets now >= nextRunAtMs as runnable. The comment says “run it ASAP … scheduler needs to know it’s runnable”, which implies returning nowMs (or similar) rather than the original atMs.

Consider rewording this comment to reflect the actual behavior (keeping atMs so it stays due until success).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cron/service/jobs.ts
Line: 46:50

Comment:
[P3] Comment doesn’t match the code behavior. `computeJobNextRunAtMs` still returns `job.schedule.atMs` for one-shot jobs, so overdue jobs remain represented as a timestamp in the past; the scheduler is what interprets `now >= nextRunAtMs` as runnable. The comment says “run it ASAP … scheduler needs to know it’s runnable”, which implies returning `nowMs` (or similar) rather than the original `atMs`.

Consider rewording this comment to reflect the actual behavior (keeping `atMs` so it stays due until success).

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 3, 2026

Additional Comments (1)

src/web/auto-reply/deliver-reply.ts
[P1] Caption trimming is inconsistent with text-only trimming. deliverWebReply now strips leading whitespace for text-only chunks, but media captions (caption = remainingText.shift() || undefined) can still start with newlines/spaces (common for LLM output), so the original formatting issue can persist for media-first replies.

This matters when mediaList.length > 0 and the first caption chunk begins with whitespace.

A minimal fix is to trimStart() (and potentially skip empty) for caption similarly to the text-only path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/web/auto-reply/deliver-reply.ts
Line: 101:103

Comment:
[P1] Caption trimming is inconsistent with text-only trimming. `deliverWebReply` now strips leading whitespace for text-only chunks, but media captions (`caption = remainingText.shift() || undefined`) can still start with newlines/spaces (common for LLM output), so the original formatting issue can persist for media-first replies.

This matters when `mediaList.length > 0` and the first caption chunk begins with whitespace.

A minimal fix is to `trimStart()` (and potentially skip empty) for `caption` similarly to the text-only path.

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

@FelixFoster FelixFoster force-pushed the fix/whatsapp-whitespace-strip branch from ecba4a7 to ad354a8 Compare February 3, 2026 14:35
@FelixFoster
Copy link
Copy Markdown
Author

Thanks @greptile-apps! Fixed in bd79b33. Added trimStart() to media captions.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 11, 2026

Perfect! The media caption fix looks good at src/web/auto-reply/deliver-reply.ts:101-102. You're properly handling the optional text by trimming rawCaption only when it exists, then using undefined for the caption if it's empty after trimming.

This closes the gap I flagged in the review. The PR now consistently trims leading whitespace across all WhatsApp text and media caption paths. 🎯

mcinteerj added a commit to mcinteerj/openclaw that referenced this pull request Feb 14, 2026
LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.
Takhoffman pushed a commit to mcinteerj/openclaw that referenced this pull request Feb 14, 2026
LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.
Takhoffman added a commit that referenced this pull request Feb 14, 2026
)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as #8052 and #10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <[email protected]>

---------

Co-authored-by: Tak Hoffman <[email protected]>
hamidzr pushed a commit to hamidzr/openclaw that referenced this pull request Feb 14, 2026
…nclaw#16158)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <[email protected]>

---------

Co-authored-by: Tak Hoffman <[email protected]>
mverrilli pushed a commit to mverrilli/openclaw that referenced this pull request Feb 14, 2026
…nclaw#16158)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <[email protected]>

---------

Co-authored-by: Tak Hoffman <[email protected]>
GwonHyeok pushed a commit to learners-superpumped/openclaw that referenced this pull request Feb 15, 2026
…nclaw#16158)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <[email protected]>

---------

Co-authored-by: Tak Hoffman <[email protected]>
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added stale Marked as stale due to inactivity and removed stale Marked as stale due to inactivity labels Feb 21, 2026
@FelixFoster FelixFoster force-pushed the fix/whatsapp-whitespace-strip branch from bd79b33 to 2467927 Compare February 26, 2026 12:14
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 1, 2026
…nclaw#16158)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <[email protected]>

---------

Co-authored-by: Tak Hoffman <[email protected]>
(cherry picked from commit 3881af5)

# Conflicts:
#	CHANGELOG.md
@mudrii

This comment was marked as spam.

hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 3, 2026
…nclaw#16158)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <[email protected]>

---------

Co-authored-by: Tak Hoffman <[email protected]>
(cherry picked from commit 3881af5)

# Conflicts:
#	CHANGELOG.md
#	src/agents/pi-embedded-helpers/errors.ts
@openclaw-barnacle openclaw-barnacle bot added the app: macos App: macos label Mar 3, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…nclaw#16158)

* fix: strip leading whitespace from sanitizeUserFacingText output

LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.

Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value

Fixes the same issue as openclaw#8052 and openclaw#10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.

* Changelog: note sanitizeUserFacingText whitespace normalization

Co-authored-by: Tak Hoffman <[email protected]>

---------

Co-authored-by: Tak Hoffman <[email protected]>
@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Mar 9, 2026
@ImLukeF
Copy link
Copy Markdown
Contributor

ImLukeF commented Mar 12, 2026

Superseded with #43539.

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

Labels

app: macos App: macos channel: whatsapp-web Channel integration: whatsapp-web size: XS stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WhatsApp outbound: strip leading whitespace from text messages

3 participants