Skip to content

fix: refuse an assignee Plane will not accept, rather than lose the field - #204

Open
Semih702 wants to merge 1 commit into
makeplane:mainfrom
Semih702:fix/assignee-silent-drop
Open

fix: refuse an assignee Plane will not accept, rather than lose the field#204
Semih702 wants to merge 1 commit into
makeplane:mainfrom
Semih702:fix/assignee-silent-drop

Conversation

@Semih702

@Semih702 Semih702 commented Aug 16, 2026

Copy link
Copy Markdown

Description

Plane filters an assignee id it will not accept out of the payload during validation, and an update deletes the work item's existing assignees before writing what is left. A write naming one therefore both fails to apply and clears the field, under a 200 — nothing in the response distinguishes it from a successful assignment.

Measured against self-managed 1.2.0, through v0.3.0's own workitem tool, where alice is a member of the project and bob is a workspace member who isn't:

request result reported
update assignees=[bob] [] — alice gone, bob never added success
update assignees=[alice, bob] [alice] — bob silently missing success
manage_assignee add_user_id=bob unchanged success

Only the first destroys data, and it is the one that bites in practice: an agent told "assign these twelve to bob", where bob isn't in that project, clears the assignee on twelve items and reports twelve successes.

Three ways to land there, none of them visible to the caller: the user is a workspace member but not a member of this project; their project role is guest, below Plane's role__gte=15 floor; or the membership is is_active=False. Both halves are on makeplane/plane preview — the filter that drops the id and the unconditional delete before the write.

It is the same failure tools/README.md already rules out for enum parameters — "dropping it writes the record without the field and reports success" — one layer out, where the value is a UUID and the API does the dropping.

What changed

_unassignable() reads the project's members and names the rejected ids before the write. Wired into create, update and manage_assignee:

Error: not assignable in this project: 3d0f6b63-…. Plane drops an assignee it will not
accept without reporting it, and clears the work item's existing assignees in the process.
Only active project members at member role or above can be assigned -- `member list_project`
lists them.

Notes:

manage_assignee validates only the incoming id rather than the merged list, so a member who has since lost project access can still be removed.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Test Scenarios

Unit — 7 tests, 11 cases in tests/tools/test_dispatch.py, beside the enum-dropping test they are a variation of:

  • the three rejection reasons: non-member, guest, inactive membership
  • a role above the floor, and one exactly on it
  • the Community Edition shape, with role absent
  • a failing member lookup, and an empty one, each letting the write through
  • a write with no assignees making no extra request
  • manage_assignee refusing an add, and a removal not being blocked

Checked against the pre-fix tree: 4 of the 11 fail without the guard, and the other 7 are the "must keep working" half that should pass either way.

1092 passed, 25 skipped. ruff check and ruff format --check clean. One existing test needed a line — test_coercion.py's recorder stops at the first SDK call, and the preflight is now that call, so it answers the member lookup rather than being consumed by it.

End to end — built the wheel, installed it into a clean venv with no editable link to the source tree, and ran plane-mcp-server stdio as a real subprocess driven over JSON-RPC against self-managed 1.2.0. 14/14:

  • create with a real member unaffected; update with a non-member refused and the original assignee still there; manage_assignee add refused with the list intact; adding a real member still works; removal not blocked; a write carrying no assignees unaffected; manage_label not caught by the assignee check.
  • Retired names: update_work_item and manage_work_item_assignee are guarded the same way; create_work_item and retrieve_work_item are unaffected.

I verified the non-member case live. Guest and inactive membership go through the same filter, so I would expect them to behave identically, but testing those means reshuffling memberships on a live workspace — they are covered by unit tests only.

References

Fixes #193. Replaces #194, which patched plane_mcp/tools/work_items.py — a file this refactor removed.

Summary by CodeRabbit

  • Bug Fixes
    • Assignee changes now verify that users are active project members with sufficient permissions before updates are saved.
    • Prevented invalid assignments from being silently dropped or existing assignments from being unintentionally cleared.
    • Assignee removals continue to work regardless of current member status.
    • Updates proceed when membership information is unavailable or inconclusive.

…ield

Plane filters an assignee id it will not accept out of the payload during
validation, and an update deletes the work item's existing assignees before
writing what is left. So a write naming one both fails to apply and clears
the field, under a 200 -- the caller cannot tell it from a success.

Three ways to land there, none visible in the response: the user is a
workspace member but not a member of this project, their project role is
guest, or the membership is inactive.

_unassignable() reads the project's members and names the rejected ids
before the write, for create, update and manage_assignee. An unreadable or
empty member list skips the check rather than blocking a write that works
today, and Community Edition's members carry no role, so an absent one
cannot disqualify.
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 05caf582-d99a-4514-887d-d4e01e5f978a

📥 Commits

Reviewing files that changed from the base of the PR and between 00d9d1f and 08d8436.

📒 Files selected for processing (3)
  • plane_mcp/tools/workitem.py
  • tests/test_coercion.py
  • tests/tools/test_dispatch.py

Included review availability: Your plan includes up to 2 reviews per rolling hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Work-item create, update, and assignee-management additions now validate assignees against active project membership and role requirements before writes. Validation skips inconclusive member lookups, while removals remain unrestricted. Tests cover these paths.

Changes

Work-item assignee validation

Layer / File(s) Summary
Assignee validation flow
plane_mcp/tools/workitem.py
The tool checks active project members against the minimum assignable role before create, update, and assignee-addition writes. It reports rejected IDs and skips validation when membership data is unavailable or inconclusive.
Validation test coverage
tests/test_coercion.py, tests/tools/test_dispatch.py
Mocks provide project-member data. Tests cover rejected and accepted assignments, lookup fallbacks, unnecessary-request avoidance, additions, and removals.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 08d84

The change rejects assignees that Plane would silently discard, preventing unintended loss of existing assignments while preserving valid assignment and removal behavior; no actionable merge-blocking risk remains after normal checks and review.

Possibly related issues

  • Issue 9517: Addresses the same silent assignee-dropping behavior through active project-membership and role validation.

Possibly related PRs

Suggested reviewers: akhil-vamshi-konam

Sequence Diagram(s)

sequenceDiagram
  participant WorkItemTool
  participant ProjectMembersAPI
  participant WorkItemAPI
  WorkItemTool->>ProjectMembersAPI: Request project members
  ProjectMembersAPI-->>WorkItemTool: Return members or lookup failure
  WorkItemTool->>WorkItemTool: Validate active role and requested IDs
  WorkItemTool->>WorkItemAPI: Send write when permitted
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy [#193] by validating assignees before writes, rejecting invalid members, and preserving removal operations.
Out of Scope Changes check ✅ Passed The implementation and tests remain within the linked issue scope of preventing silent assignee failures and data loss.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: reject invalid assignees before Plane silently drops them and risks clearing existing assignments.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

Bug: assigning a non-project-member silently clears a work item's existing assignees

1 participant