All-clear image URL contains a literal space; Webex rejects it with HTTP 400 #152
Labels
No labels
area:ai
area:ci-cd
area:notifications
area:observability
area:public-pages
backlog
bug
duplicate
enhancement
help wanted
invalid
question
type:decision
type:feature
type:infra
type:maintenance
type:security
v1.0.1
v1.1.0
v1.2.0
v1.3.0
v2.0.0
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
rbrooks/WeatherBot#152
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Root cause of the Webex 400 loop reported in #148. #148 fixed the unbounded retry; this is the underlying rejection.
Diagnosis
_format_iastate_valid(app/services/radar.py:198-205) returns Iowa State's valid segment as'YYYY-MM-DD HHMM'— with a literal space:_iastate_allclear_image_url(app/services/notifiers/webhook.py:66-89) prepends that as a path segment, producing:That string is handed to Webex as a
files[]URL. Webex validates it, finds it is not a valid URI, and rejects the whole message with HTTP 400 — which is exactly the failure looping in production on channele70842aa.Confirmed empirically
Why only the all-clear path
_iastate_alert_image_urlbuilds the same URL without thevalid:segment, so it has no space and regular alerts send fine. Thevalid:segment exists only on the all-clear variant. That asymmetry is why alerts work and all-clears fail, and it is what pointed at the cause.Why the server-side fetch never noticed
radar.py:234uses the same helper forfetch_iastate_warning_image, but there the string is passed to httpx, which percent-encodes the space when building the request. Only the "hand the raw string to a third party" path is broken.Other affected consumers
The same malformed URL is produced for:
app/services/notifiers/webex.py:307— confirmed hard failure (HTTP 400, message never sent)app/services/notifiers/webhook.py:647and:671— Discord embed / webhook payloadsapp/api/public.py:185— the public alert page imageBehaviour in the others depends on how tolerant each consumer is (browsers percent-encode a
srcautomatically, so the public page probably renders). Webex is simply the strictest. The URL is malformed regardless and should be fixed at the source.Fix
Percent-encode the
validvalue in_iastate_allclear_image_url, the one place that emits a URL string for external consumption. Leave_format_iastate_validreturning the human format — it is documented as such and the server-side fetch path relies on httpx encoding it, so encoding there risks double-encoding.Fixed in #153 (merged). CI green in 3m6s. 815 SQLite-tier tests pass.
quote(..., safe="")on the valid value at the point of use, sovalid:2026-07-24 1716becomesvalid:2026-07-24%201716and the URL is a proper URI again. Deliberately not applied inside_format_iastate_valid, which the server-side fetch path relies on httpx to encode — doing it there would risk double-encoding.Tests assert no raw whitespace anywhere in the URL,
%20present, segment separators and the.pngsuffix intact, and that it parses as a proper URI — plus a control that the alert variant is unchanged and still carries novalid:segment.Not yet live. See the deployment note on #148: the running stack is not the one CD deploys to, so this fix is on
mainbut not in production.