[M3] Publish path omits User-Agent and reports a WAF 403 as retryable — the b99cbdf fix does not cover product create #79

Closed
opened 2026-09-20 18:16:46 +00:00 by grokbot_ty · 1 comment
Owner

Summary

b99cbdf fixed a 403 from the store's WAF by sending a descriptive User-Agent from the Woo client. That fix landed in woo.py only. The publish path builds its own requests and omits the header, so create is still exposed to the same 403 — and the 403 is then classified as a retryable failure, telling the owner to retry something retrying cannot fix.

Found in a read-only review of main @ b99cbdf (2026-09-20). 63 backend tests pass and both docs gates pass at that commit; these are gaps the green suite does not cover.

Observed evidence

backend/publish.py:204-212 (_sku_exists) and backend/publish.py:215-224 (_create_product) call client._request directly with hand-built header dicts. A recording transport shows what each path actually sends:

GET  /wp-json/wc/v3/products/categories?per_page=100&page=1   -> ['Accept','Authorization','User-Agent']
GET  /wp-json/wc/v3/products?sku=SLAB-01                      -> ['Accept','Authorization']                 UA MISSING
POST /wp-json/wc/v3/products                                  -> ['Accept','Authorization','Content-Type']  UA MISSING

Reproduce: backend.woo.WooClient.from_settings(base, user, enc, request=<recorder>), then call publish._sku_exists(client, "SLAB-01") and publish._create_product(client, {}) and inspect the recorded headers. Compare against client._get("/products/categories").

Two compounding problems:

  1. woo.py:63-69 raises WooForbiddenError on 403, but _sku_exists/_create_product swallow the response and raise a bare WooError (publish.py:211, :223), so the typed error never reaches _classify (publish.py:104-112).
  2. The fallthrough in _classify returns recoverable = True ("The store create failed. You can retry."). A WAF block is not recoverable by retrying, so the owner gets a message that invites a loop, and PUBLISH-35/36 (classify recoverable vs unrecoverable) is effectively wrong for the one failure we know this store produces.

Acceptance

  • The publish path issues its product requests through the same transport as the rest of the client (_get / a new client method), inheriting User-Agent and the 401/403/404 typing.
  • A mocked 403 on SKU lookup and on product create is classified as unrecoverable with a plain message that names the store blocking the request, not "retry".
  • A transport-level test asserts the User-Agent header on every outbound Woo request, so this cannot regress silently again.

Related

  • Commit b99cbdf (User-Agent + WooForbiddenError), backend/woo.py:18,63-69,106,149
  • Requirements: PUBLISH-30, PUBLISH-35, PUBLISH-36, QUALITY-35, QUALITY-120
## Summary `b99cbdf` fixed a 403 from the store's WAF by sending a descriptive `User-Agent` from the Woo client. That fix landed in `woo.py` only. The publish path builds its own requests and omits the header, so create is still exposed to the same 403 — and the 403 is then classified as a *retryable* failure, telling the owner to retry something retrying cannot fix. Found in a read-only review of `main` @ `b99cbdf` (2026-09-20). 63 backend tests pass and both docs gates pass at that commit; these are gaps the green suite does not cover. ## Observed evidence `backend/publish.py:204-212` (`_sku_exists`) and `backend/publish.py:215-224` (`_create_product`) call `client._request` directly with hand-built header dicts. A recording transport shows what each path actually sends: ``` GET /wp-json/wc/v3/products/categories?per_page=100&page=1 -> ['Accept','Authorization','User-Agent'] GET /wp-json/wc/v3/products?sku=SLAB-01 -> ['Accept','Authorization'] UA MISSING POST /wp-json/wc/v3/products -> ['Accept','Authorization','Content-Type'] UA MISSING ``` Reproduce: `backend.woo.WooClient.from_settings(base, user, enc, request=<recorder>)`, then call `publish._sku_exists(client, "SLAB-01")` and `publish._create_product(client, {})` and inspect the recorded headers. Compare against `client._get("/products/categories")`. Two compounding problems: 1. `woo.py:63-69` raises `WooForbiddenError` on 403, but `_sku_exists`/`_create_product` swallow the response and raise a bare `WooError` (`publish.py:211`, `:223`), so the typed error never reaches `_classify` (`publish.py:104-112`). 2. The fallthrough in `_classify` returns `recoverable = True` ("The store create failed. You can retry."). A WAF block is not recoverable by retrying, so the owner gets a message that invites a loop, and PUBLISH-35/36 (classify recoverable vs unrecoverable) is effectively wrong for the one failure we know this store produces. ## Acceptance - The publish path issues its product requests through the same transport as the rest of the client (`_get` / a new client method), inheriting `User-Agent` and the 401/403/404 typing. - A mocked 403 on SKU lookup and on product create is classified as unrecoverable with a plain message that names the store blocking the request, not "retry". - A transport-level test asserts the `User-Agent` header on every outbound Woo request, so this cannot regress silently again. ## Related - Commit `b99cbdf` (User-Agent + WooForbiddenError), `backend/woo.py:18,63-69,106,149` - Requirements: PUBLISH-30, PUBLISH-35, PUBLISH-36, QUALITY-35, QUALITY-120
grokbot_ty added the blocking label 2026-09-20 18:16:46 +00:00
Author
Owner

Closed by M3 Option A: main 0c00d25 + UAT QUALITY-110 evidence.

  • Deploy: version 0.5.0-m3, woo_reachable: true
  • test-woo ok (taxonomy synced)
  • Inference off for the walk; SLAB-UAT-125846 → Woo draft product_id=48147 matching reviewed ready set
  • Must-fix covered: #79 UA/403, #82 live current_options, #83 unknown-field/taxonomy 4xx, #84 health probe+version, #89 Create title/short from review, #98 live-edge from synced edge term + attr slug resolve
Closed by M3 Option A: main `0c00d25` + UAT QUALITY-110 evidence. - Deploy: version `0.5.0-m3`, `woo_reachable: true` - `test-woo` ok (taxonomy synced) - Inference off for the walk; **SLAB-UAT-125846** → Woo draft `product_id=48147` matching reviewed ready set - Must-fix covered: #79 UA/403, #82 live `current_options`, #83 unknown-field/taxonomy 4xx, #84 health probe+version, #89 Create title/short from review, #98 live-edge from synced edge term + attr slug resolve
Sign in to join this conversation.
No Label blocking
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Ty_Tech/SlabUploader#79