Skip to content

plane: REST + webhook dual-shape decode for WorkItem refs (PFB-25)#13

Merged
hstern merged 1 commit into
mainfrom
fix/pfb-25-rest-decode-both-shapes
May 24, 2026
Merged

plane: REST + webhook dual-shape decode for WorkItem refs (PFB-25)#13
hstern merged 1 commit into
mainfrom
fix/pfb-25-rest-decode-both-shapes

Conversation

@hstern

@hstern hstern commented May 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds custom UnmarshalJSON on StateRef, LabelRef, AssigneeRef so each accepts both the REST bare-UUID form and the webhook object form.
  • Decoupling per-type means WorkItem stays declarative and []LabelRef / []AssigneeRef automatically dispatch to the element method — works for []string (REST) and []object (webhook) without per-field wrappers.
  • Regression tests pin verbatim captures of the real Plane CE v1.3.1 REST POST /issues/ response at both the type level (TestWorkItem_UnmarshalJSON_RESTShape_PFB25) and through the HTTP client (TestCreateIssue_RealPlaneRESTShape_PFB25). Webhook-shape tests are unchanged and continue to pass (TestParse_RealPlaneWorkItemPayload_PFB24).

Why

v0.1.2 fixed PFB-24 by typing the refs as objects to match webhook deliveries. That broke every forge → plane create call because Plane's REST surface returns the same fields as bare UUID strings (state) and arrays of bare UUID strings (labels, assignees). Symptom on every create:

ERROR translator failed side="forge issue" status=500
  err="sync: create issue: plane: decode POST .../issues/ response:
       json: cannot unmarshal string into Go struct field WorkItem.state of type plane.StateRef"

Since the POST already succeeded on the Plane side, the bridge had no record of the new Plane UUID; the loop-break LRU is populated only on translator success, so Plane's webhook for the new work item then duplicated back to Forgejo as a brand-new issue. See PFB-25 body for the full trace.

Test plan

  • make race — all packages green
  • make lint — 0 issues
  • Verified the REST + webhook shapes against real Plane CE v1.3.1 (plane.stern.ca) via probe curl before designing the fix; both fixtures in the new tests are verbatim from those captures
  • Manual rollout verification: pull :0 on the bridge host, retry a forge→plane create against ascend-cloud (the repo that surfaced the bug), confirm no duplicate Forgejo issue

Broader testdata audit + real-Plane round-trip e2e tracked separately so this PR stays focused on unblocking the rollout.

🤖 Generated with Claude Code

v0.1.2 (PFB-24) typed StateRef/LabelRef/AssigneeRef to match webhook
deliveries, where Plane sends objects. Plane CE v1.3.1's REST surface
(POST/GET/PATCH /issues/) returns the same fields as bare UUID strings,
so every forge->plane create call failed decoding the POST response and
silently left a duplicate Forgejo issue once Plane's own webhook came
back. Custom UnmarshalJSON on each ref accepts either shape; regression
tests pin both forms against verbatim captures from plane.stern.ca.

Refs PFB-25.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hstern hstern merged commit 3e6f6fc into main May 24, 2026
5 checks passed
@hstern hstern deleted the fix/pfb-25-rest-decode-both-shapes branch May 24, 2026 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant