Bug: /v1/digitize 500s when input SVG uses CSS named colors ('white', 'black', etc.) — color not normalized before StitchObject validation #19

Closed
opened 2026-09-01 16:29:55 +00:00 by Mark · 1 comment
Owner

Summary

POST /v1/digitize returns HTTP 500 with invalid hex color: 'white' when the input SVG (or any raster converted from such an SVG) uses a CSS named color like fill="white" instead of a #RRGGBB hex literal.

Steps to Reproduce

  1. Upload an SVG using a named color to /v1/digitize:
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200" height="100">
      <rect width="200" height="100" fill="white"/>
      <text x="100" y="78" font-family="serif" font-size="92" font-weight="900"
            text-anchor="middle" fill="black">BB</text>
    </svg>
    
  2. multipart form: image (the SVG, image/svg+xml) + settings (JSON; e.g. {"hoop_size_mm":{"w":100,"h":100}}).
  3. Server response: HTTP 500 {"error":"internal","request_id":"<id>"}.

Expected

thread_color should accept CSS named colors (white, black, red, blue, …) and either normalize them to #RRGGBB at the engine→service boundary, OR reject them with a 422 with a clear message about using hex colors. The current behavior is a 500 with no actionable feedback.

Actual

Server traceback (from /tmp/easystitch-health.log):

File "/home/b/Projects/easystitch-service/pipeline_runner.py", line 168, in _flush
    ServiceStitchObject(
File "/home/b/Projects/easystitch-service/pipeline_runner.py", line 259, in _group_stitches_into_objects
    self._pipeline = EmbroideryPipeline()
File "/home/b/Projects/easystitch-service/models.py", line 103, in _check_color
    return _validate_hex(v)
pydantic_core._pydantic_core.ValidationError: 1 validation error for StitchObject
thread_color
  Value error, invalid hex color: 'white'

Root Cause

_validate_hex in models.py:26 only accepts strings shaped #RRGGBB. _flush in pipeline_runner.py:163-175 constructs a ServiceStitchObject with thread_color=current_color or "#000000" — the or fallback only fires when current_color is falsy (None/""). 'white' is truthy, so the invalid string is passed straight to pydantic, which 500s.

The engine (src/main_pipeline) emits stitches with color set to whatever string the SVG/raster color came back as. CSS named colors (white, black, red, etc.) and possibly rgb(...) / rgba(...) forms flow through unchecked.

Suggested Fix

Pick one (in order of preference):

  1. Normalize at the boundary in _group_stitches_into_objects (pipeline_runner.py:163-175) before constructing ServiceStitchObject. A small css_named_to_hex() helper plus handling for rgb(...) would make the pipeline self-healing and keep the API contract strict.
  2. Relax the validator in models.py:103-106 to accept named colors by calling a shared normalizer. Risk: changes the public API contract in a potentially breaking way (callers may rely on strict hex).
  3. Reject at the digitize endpoint with a 422 listing offending named colors. Useful but a poor UX since the engine already accepted the image.

Environment

  • easystitch-service running on localhost:8080 (PID 1457239, started 2026-08-29)
  • Tested from the shakedown session: BB monogram with fill="white" and fill="black" → 500
  • Re-tested with explicit #FFFFFF/#000000 → digitize returns 200 with totalStitches: 6572, colorCount: 2 but regions: [] and stitchObjects: [] (separately, may be related — see follow-up notes)

Severity

High for any non-power-user who uploads SVGs from design tools — most design exports use named colors. The 500 + opaque request_id also obscures the cause from the GUI; the user sees a generic error and has no recourse.

Shakedown Context

This came out of the BB-monogram GUI shakedown on 2026-09-01. Real artifact at /tmp/shakedown-bb/bb.svg. Request_id captured for the failing call: c08a44f1230e (and 32d35034798d on a repeat).

## Summary `POST /v1/digitize` returns HTTP 500 with `invalid hex color: 'white'` when the input SVG (or any raster converted from such an SVG) uses a CSS named color like `fill="white"` instead of a `#RRGGBB` hex literal. ## Steps to Reproduce 1. Upload an SVG using a named color to `/v1/digitize`: ```xml <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="200" height="100"> <rect width="200" height="100" fill="white"/> <text x="100" y="78" font-family="serif" font-size="92" font-weight="900" text-anchor="middle" fill="black">BB</text> </svg> ``` 2. multipart form: `image` (the SVG, `image/svg+xml`) + `settings` (JSON; e.g. `{"hoop_size_mm":{"w":100,"h":100}}`). 3. Server response: HTTP 500 `{"error":"internal","request_id":"<id>"}`. ## Expected `thread_color` should accept CSS named colors (white, black, red, blue, …) and either normalize them to `#RRGGBB` at the engine→service boundary, OR reject them with a 422 with a clear message about using hex colors. The current behavior is a 500 with no actionable feedback. ## Actual Server traceback (from `/tmp/easystitch-health.log`): ``` File "/home/b/Projects/easystitch-service/pipeline_runner.py", line 168, in _flush ServiceStitchObject( File "/home/b/Projects/easystitch-service/pipeline_runner.py", line 259, in _group_stitches_into_objects self._pipeline = EmbroideryPipeline() File "/home/b/Projects/easystitch-service/models.py", line 103, in _check_color return _validate_hex(v) pydantic_core._pydantic_core.ValidationError: 1 validation error for StitchObject thread_color Value error, invalid hex color: 'white' ``` ## Root Cause `_validate_hex` in `models.py:26` only accepts strings shaped `#RRGGBB`. `_flush` in `pipeline_runner.py:163-175` constructs a `ServiceStitchObject` with `thread_color=current_color or "#000000"` — the `or` fallback only fires when `current_color` is falsy (None/""). `'white'` is truthy, so the invalid string is passed straight to pydantic, which 500s. The engine (src/main_pipeline) emits stitches with `color` set to whatever string the SVG/raster color came back as. CSS named colors (`white`, `black`, `red`, etc.) and possibly `rgb(...)` / `rgba(...)` forms flow through unchecked. ## Suggested Fix Pick one (in order of preference): 1. **Normalize at the boundary** in `_group_stitches_into_objects` (pipeline_runner.py:163-175) before constructing `ServiceStitchObject`. A small `css_named_to_hex()` helper plus handling for `rgb(...)` would make the pipeline self-healing and keep the API contract strict. 2. **Relax the validator** in `models.py:103-106` to accept named colors by calling a shared normalizer. Risk: changes the public API contract in a potentially breaking way (callers may rely on strict hex). 3. **Reject at the digitize endpoint** with a 422 listing offending named colors. Useful but a poor UX since the engine already accepted the image. ## Environment - `easystitch-service` running on `localhost:8080` (PID 1457239, started 2026-08-29) - Tested from the shakedown session: BB monogram with `fill="white"` and `fill="black"` → 500 - Re-tested with explicit `#FFFFFF`/`#000000` → digitize returns 200 with `totalStitches: 6572, colorCount: 2` but `regions: []` and `stitchObjects: []` (separately, may be related — see follow-up notes) ## Severity **High** for any non-power-user who uploads SVGs from design tools — most design exports use named colors. The 500 + opaque request_id also obscures the cause from the GUI; the user sees a generic error and has no recourse. ## Shakedown Context This came out of the BB-monogram GUI shakedown on 2026-09-01. Real artifact at `/tmp/shakedown-bb/bb.svg`. Request_id captured for the failing call: `c08a44f1230e` (and `32d35034798d` on a repeat).
Mark closed this issue 2026-09-04 15:06:46 +00:00
Author
Owner

Fixed and pushed to main (commit 530920f).

Fix: Normalize at the engine→service boundary in _group_stitches_into_objects (pipeline_runner.py). New _normalize_color() helper handles CSS named colors (white/black/red/etc.), rgb(r, g, b) / rgba(...) (clamped to [0, 255]), and already-hex #RRGGBB / #RGB (case preserved). Anything unrecognised falls back to #000000 — a black stitch is better than a 500 that takes the whole export down.

Tests added in tests/unit/test_pipeline_runner.py:

  • Parametrized _normalize_color for named/hex/rgb/rgba/unknown inputs (24 cases)
  • _group_stitches_into_objects named-color regression — the actual repro: white/black flows now produce #FFFFFF/#000000 instead of crashing pydantic.

121/121 tests pass, ruff clean.

Note: kilo exited 0 on the quickfix dispatch without committing source changes — same pattern as #53. Per the fixer-pipeline skill, fell back to manual fix. Also worth flagging separately: the quickfix gate currently uses the system python (hermes-agent venv, FastAPI 0.115.6) instead of the repo venv (0.115.14), which would have made every Python-repo gate fail until that is fixed — but that is a separate gate-script issue, not part of this bug.

Fixed and pushed to main (commit `530920f`). **Fix:** Normalize at the engine→service boundary in `_group_stitches_into_objects` (pipeline_runner.py). New `_normalize_color()` helper handles CSS named colors (`white`/`black`/`red`/etc.), `rgb(r, g, b)` / `rgba(...)` (clamped to [0, 255]), and already-hex `#RRGGBB` / `#RGB` (case preserved). Anything unrecognised falls back to `#000000` — a black stitch is better than a 500 that takes the whole export down. **Tests added** in `tests/unit/test_pipeline_runner.py`: - Parametrized `_normalize_color` for named/hex/rgb/rgba/unknown inputs (24 cases) - `_group_stitches_into_objects` named-color regression — the actual repro: `white`/`black` flows now produce `#FFFFFF`/`#000000` instead of crashing pydantic. 121/121 tests pass, ruff clean. Note: kilo exited 0 on the quickfix dispatch without committing source changes — same pattern as #53. Per the fixer-pipeline skill, fell back to manual fix. Also worth flagging separately: the quickfix gate currently uses the system `python` (hermes-agent venv, FastAPI 0.115.6) instead of the repo venv (0.115.14), which would have made every Python-repo gate fail until that is fixed — but that is a separate gate-script issue, not part of this bug.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Mark/easystitch-service#19
No description provided.