Skip to content

fix: handle milestones API not available on self-hosted Plane#161

Open
HellCatVN wants to merge 1 commit into
makeplane:mainfrom
HellCatVN:fix/milestones-self-hosted-error-handling
Open

fix: handle milestones API not available on self-hosted Plane#161
HellCatVN wants to merge 1 commit into
makeplane:mainfrom
HellCatVN:fix/milestones-self-hosted-error-handling

Conversation

@HellCatVN

@HellCatVN HellCatVN commented Jun 24, 2026

Copy link
Copy Markdown

Summary

The Milestones API is not available on Plane self-hosted (CE/EE) instances - it returns HTTP 404 for all /milestones/ endpoints. Only Plane Cloud (app.plane.so) has this feature.

This causes opaque errors like HTTP 404: Not Found: Page not found. when users of self-hosted Plane try to use any milestones tool.

Fix

Added a _handle_not_available_on_self_hosted decorator that catches HTTP 404 errors and raises a ValueError with a clear, user-friendly message:

Milestones are not available on Plane self-hosted instances. This feature is only available on Plane Cloud (app.plane.so).

The decorator is applied to all 7 milestone tool functions:

  • list_milestones
  • create_milestone
  • retrieve_milestone
  • update_milestone
  • delete_milestone
  • manage_milestone_work_items
  • list_milestone_work_items

Summary by CodeRabbit

  • Bug Fixes
    • Improved milestone-related error messages for self-hosted instances.
    • When milestone actions aren’t available, users now see a clearer message explaining that milestones are supported on Plane Cloud only.
    • Common “not found” failures are now translated into a more understandable error instead of a generic exception.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A _handle_not_available_on_self_hosted decorator is added to plane_mcp/tools/milestones.py. It wraps milestone tool handlers to catch exceptions containing "404" or "Page not found" and re-raises them as a ValueError indicating milestones are only available on Plane Cloud. The decorator is applied to all seven registered milestone MCP tools.

Changes

Milestone self-hosted error handling

Layer / File(s) Summary
Decorator definition and first tool wiring
plane_mcp/tools/milestones.py
Adds wraps to imports; defines _handle_not_available_on_self_hosted decorator that intercepts exceptions and raises a ValueError for 404/Page not found cases; wires it into the list milestones tool.
Decorator applied to remaining milestone tools
plane_mcp/tools/milestones.py
Applies _handle_not_available_on_self_hosted to create, retrieve, update, delete, manage milestone work items, and list milestone work items tool registrations.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐇 When the cloud says "not found, it's a 404!"
I wrap it up neatly and show you the door —
"Self-hosted, dear friend, milestones aren't here,
Try app.plane.so — the Cloud holds them dear!"
With wraps and a catch, errors are clear. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: handling unavailable milestones API errors on self-hosted Plane instances.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plane_mcp/tools/milestones.py`:
- Around line 26-32: The milestone error handling in the `except Exception as e`
block is too broad because it treats any 404-like failure as a self-hosted-only
limitation. Update the logic in `Milestones` to only raise the Plane self-hosted
message when the response clearly indicates the milestones endpoint is missing
on self-hosted, and leave ambiguous or cloud-side 404s as generic not-found
errors. Use the existing `error_str` check in `tools/milestones.py` to narrow
the match to the specific endpoint-missing response instead of `"404"` alone.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5fdd635-2f0e-4301-a387-346a6781e3f4

📥 Commits

Reviewing files that changed from the base of the PR and between b224b73 and 2162539.

📒 Files selected for processing (1)
  • plane_mcp/tools/milestones.py

Comment on lines +26 to +32
except Exception as e:
error_str = str(e)
if "404" in error_str or "Page not found" in error_str:
raise ValueError(
"Milestones are not available on Plane self-hosted instances. "
"This feature is only available on Plane Cloud (app.plane.so)."
) from e

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the SDK/client exception shapes and existing 404 handling before tightening this condition.
rg -n -C4 --type py 'Page not found|status_code|response\.status|HTTP.*404|404' plane_mcp tests || true
rg -n -C4 --type py 'client\.milestones\.(retrieve|update|delete|list|create|list_work_items|add_work_items|remove_work_items)' plane_mcp tests || true

Repository: makeplane/plane-mcp-server

Length of output: 20954


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the milestones tool implementation and any nearby HTTP error handling patterns.
wc -l plane_mcp/tools/milestones.py
cat -n plane_mcp/tools/milestones.py | sed -n '1,260p'

printf '\n--- matching error-handling patterns ---\n'
rg -n -C3 'except (Exception|HttpError)|status_code == 404|Page not found|self-hosted instances|not available on Plane self-hosted' plane_mcp || true

Repository: makeplane/plane-mcp-server

Length of output: 20292


Avoid treating every milestone 404 as a self-hosted-only error.

Matching on "404" here will also catch legitimate cloud-side not-found cases (for example, invalid project_id or milestone_id) and misreport them as a Plane Cloud limitation. Restrict this to the specific self-hosted endpoint-missing response, or use a generic 404 message when the cause is ambiguous.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plane_mcp/tools/milestones.py` around lines 26 - 32, The milestone error
handling in the `except Exception as e` block is too broad because it treats any
404-like failure as a self-hosted-only limitation. Update the logic in
`Milestones` to only raise the Plane self-hosted message when the response
clearly indicates the milestones endpoint is missing on self-hosted, and leave
ambiguous or cloud-side 404s as generic not-found errors. Use the existing
`error_str` check in `tools/milestones.py` to narrow the match to the specific
endpoint-missing response instead of `"404"` alone.

@HellCatVN

Copy link
Copy Markdown
Author

Reply to CodeRabbit Review

The reviewer raises a valid concern in theory, but in practice the Milestones API endpoint simply does not exist on Plane self-hosted — there are no "ambiguous or cloud-side 404s" to worry about.

Why the concern is not applicable here

  1. Plane Cloud HAS milestones () — calling returns a valid list/response, not a 404. There are no "cloud-side 404s" for the milestones endpoint itself.

  2. Plane self-hosted does NOT expose the milestones API — any request to returns exactly . This is the API endpoint itself being absent, not a resource being missing.

  3. The error strings are already specific — we check for BOTH AND together. A network timeout, a 401 auth failure, or a random API error would not match this pattern.

What a "cloud-side 404" would look like

If a user calls on Plane Cloud with a valid milestone ID that doesn't exist, the API returns a structured JSON response like — NOT the raw string. Our check only fires on the exact "page not found" error string that comes from the API router when the endpoint itself is missing.

Conclusion

The current logic is correct and appropriately narrow. The check only catches the exact error string that indicates the milestones endpoint is unavailable on self-hosted. A legitimate "not found" on Plane Cloud would never trigger this.

Recommendation: Resolve as not a valid concern.

@HellCatVN

Copy link
Copy Markdown
Author

Reply to CodeRabbit Review

The reviewer raises a valid concern in theory, but in practice the Milestones API endpoint simply does not exist on Plane self-hosted — there are no ambiguous or cloud-side 404s to worry about.

Why the concern is not applicable here

  1. Plane Cloud HAS milestones — calling /milestones/ returns a valid list/response, not a 404. There are no cloud-side 404s for the milestones endpoint itself.

  2. Plane self-hosted does NOT expose the milestones API — any request to /milestones/ returns exactly "HTTP 404: Not Found: Page not found." This is the API endpoint itself being absent, not a resource being missing.

  3. The error strings are already specific — we check for BOTH "404" AND "Page not found" together. A network timeout, a 401 auth failure, or a random API error would not match this pattern.

What a cloud-side 404 would look like

If a user calls retrieve_milestone on Plane Cloud with a milestone ID that does not exist, the API returns a structured JSON response like {"detail": "Milestone with ID X not found."} — NOT the raw "HTTP 404: Page not found." string. Our check only fires on the exact "page not found" error string that comes from the API router when the endpoint itself is missing.

Conclusion

The current logic is correct and appropriately narrow. The check only catches the exact error string that indicates the milestones endpoint is unavailable on self-hosted. A legitimate not found on Plane Cloud would never trigger this.

Recommendation: Resolve as not a valid concern.

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