Skip to content

plane: WorkItem state/labels/assignees are objects, not UUIDs#12

Merged
hstern merged 2 commits into
mainfrom
fix/plane-workitem-object-fields
May 24, 2026
Merged

plane: WorkItem state/labels/assignees are objects, not UUIDs#12
hstern merged 2 commits into
mainfrom
fix/plane-workitem-object-fields

Conversation

@hstern

@hstern hstern commented May 24, 2026

Copy link
Copy Markdown
Owner

Fixes the second real-deployment regression after v0.1.1's action-tense fix.

The bug

Real Plane CE v1.3.1 serializes nested objects for state, labels, and assignees in the work_item webhook payload:

```json
"state": {"id":"...","name":"Backlog","color":"#60646C","group":"backlog"},
"labels": [{"id":"...","name":"claude:DOCS-25","color":"#FAB287"}],
"assignees": [{"id":"...","display_name":"Alice"}]
```

v0.1.0 / v0.1.1 modelled them as `string` / `[]string`, so every real Plane webhook 400'd on payload decode after the action-tense fix unblocked the verb-mismatch silent-drop:

```
WARN plane: malformed payload: work item: json: cannot unmarshal object into Go struct field WorkItem.state of type string
```

The fix

  1. `internal/plane/types.go` — added `StateRef`, `LabelRef`, `AssigneeRef` types and pointed the `WorkItem` fields at them.
  2. `internal/sync/planeworkitem.go` — extract UUIDs from `evt.WorkItem.Labels` at the one read site that consumed them (the resolver helper still takes `[]string` UUIDs).
  3. `internal/plane/testdata/work_item_{created,updated}.json` — updated to the object form (the wire reality).
  4. Regression test `TestParse_RealPlaneWorkItemPayload_PFB24` pins decoding of a captured-from-postgres real Plane payload so the next struct-shape drift fails in CI instead of production.

Docs in this PR

Following the new convention of shipping docs with the fix:

  • `CHANGELOG.md` — added in Keep-a-Changelog format with entries for 0.1.0 / 0.1.1 / 0.1.2.
  • `README.md` — status header bumped to v0.1.2, image tags updated, "What ships" section reworded to v0.1 (not pinned to a single patch release).
  • `internal/plane/README.md` — "Plane API notes" section gains a #0 entry documenting the nested-object wire shape and pointing at the regression test.

Test plan

  • lint
  • build-image
  • e2e-docker (forgejo)
  • e2e-docker (gitea)

When merged, tag v0.1.2 — this is a real-deployment-broken bug just like v0.1.1, same patch-release cadence.

🤖 Generated with Claude Code

hstern and others added 2 commits May 24, 2026 00:12
Fixes the second real-deployment regression after v0.1.1's action-tense
fix. Real Plane CE v1.3.1 serializes nested objects for state, labels,
and assignees:

    "state": {"id":"...","name":"Backlog","color":"#60646C","group":"backlog"},
    "labels": [{"id":"...","name":"claude:DOCS-25","color":"#FAB287"}],
    "assignees": [{"id":"...","display_name":"Alice"}]

v0.1.0 / v0.1.1 modelled them as `string` / `[]string` so every real
Plane webhook 400'd on payload decode after the action-tense fix
unblocked the verb-mismatch silent-drop.

Three changes:

1. internal/plane/types.go — added StateRef, LabelRef, AssigneeRef
   types and pointed the WorkItem fields at them.

2. internal/sync/planeworkitem.go — extract UUIDs from
   evt.WorkItem.Labels at the one read site that consumed them. The
   resolver helper still takes []string; the LabelRef.Name field is
   in-band on the wire now (so we could skip the UUID→name lookup),
   but keeping the indirection means resolvePlaneLabelNames stays the
   single source of truth for label naming.

3. internal/plane/testdata/work_item_{created,updated}.json updated
   to the object form. Regression test
   TestParse_RealPlaneWorkItemPayload_PFB24 pins decoding of a
   captured-from-postgres real Plane payload so the next struct-shape
   drift fails in CI instead of in production.

Docs in the same PR per the new convention:
- CHANGELOG.md added (Keep-a-Changelog format, entries for 0.1.0/0.1.1/0.1.2)
- README.md status header bumped to v0.1.2, image tags updated
- internal/plane/README.md Plane API notes section gains a #0 entry
  documenting the nested-object wire shape

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Plane→forge e2e step builds a synthetic webhook payload with
bare-string state, which the v0.1.2 type change rejects with the
exact 400-malformed-payload error this PR fixes. The synthetic
payload now matches the real Plane wire shape: StateRef object
with id + name + color + group. Same UUID; just nested in the
object the parser now expects.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@hstern hstern merged commit 847ec90 into main May 24, 2026
5 checks passed
@hstern hstern deleted the fix/plane-workitem-object-fields branch May 24, 2026 03:20
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