Skip to content

Proposal: Plane CE (Community Edition) event-transport rail #1253

@KevinLemaMartinez

Description

@KevinLemaMartinez

Context

The main motivation for this rail is that Plane offers a self-hosted Community Edition (the same codebase you can deploy on your own infra), whereas Linear is subscription-only and cloud-only. For operators who can't or won't use a SaaS issue tracker — homelab self-hosters, compliance/data-residency constraints, cost-sensitive teams — Linear is off the table. Plane CE is the natural alternative and unlocks Cyrus for that whole segment.

Plane's "Agents Beta" framework, however, only ships in Plane Commercial — Plane CE users can't use the Linear-Agent-SDK pattern. To make Cyrus work with self-hosted Plane CE I built a parallel transport package (cyrus-plane-event-transport) and wired it into the edge-worker, mirroring the GitHub/GitLab "own lane" pattern rather than touching the AgentEventTransportConfig discriminated union.

I'm opening this issue before submitting a PR to gauge whether the maintainers want this rail upstream, since it's a non-trivial chunk of code (~4.3k LOC across core, a new package, and edge-worker).

Approach (high-level)

  • New package packages/plane-event-transport/: webhook receiver (Fastify, HMAC X-Plane-Signature verify), payload translator (emits canonical PlaneAgentEvent matching Linear's shape), REST client (PlaneIssueTrackerService).
  • packages/edge-worker/src/ additions: PlaneSessionRunner (orchestrates ack → worktree → ClaudeRunner.startStreaming), PlaneClaudeActivityPoster (maps SDKMessage → Plane issue comments), PlaneSessionStore (persists last claudeSessionId per issue id to ~/.cyrus/plane-sessions.json for cross-restart resume), handlePlaneEvent dispatcher.
  • packages/core: a handful of optional RepositoryConfig fields (planeProjectId, planeAgentLabelIds, planeBypassPermissions, planeMaxTurns). Backwards-compatible — all .optional().
  • Multi-turn parity with Linear's handlePromptWithStreamingCheck: live runner + supportsStreamingInputaddStreamMessage; dead runner with persisted claudeSessionId → spawn new runner with resumeSessionId; no prior session → post "reassign to start" guidance.

Evidence

  • Public fork: https://github.com/KevinLemaMartinez/cyrus (branch feat/plane-event-transport, rebased on cyrusagents/cyrus@main v0.2.57).
  • E2E smoke against real Plane CE 1.3.1 (self-hosted) passed end-to-end: assignment → ack → worktree → Claude session → live addStreamMessage mid-session → session_completed (87 messages) → persistence write → resume after PR with resumeSessionId from the store. All three multi-turn branches verified in production logs.
  • Tests: 25 in cyrus-plane-event-transport, 17 net new in cyrus-edge-worker (PlaneSessionStore + PlaneSessionRunner extensions + label-filter helper). Full pnpm test:packages:run green.
  • 28 commits, conventional commits, focused per concern (one runner refactor / one store / etc).

Open design questions for maintainers

  1. planeBypassPermissions default: ships true because PlaneSessionRunner has no onAskUserQuestion plumbing — without bypass, the first Edit/Write/Bash hangs waiting for an approver. Acceptable for the POC; would you require false default + a permission-prompt-via-Plane-comment mechanism before considering merge?
  2. F1 test drive: CLAUDE.md mandates F1 for "major work". F1 is Linear-only today. Would a manual smoke checklist (against a self-hosted Plane CE) be acceptable, or do you want a Plane equivalent of F1?
  3. Scope split: 4.3k LOC in one PR is heavy. Happy to split into 3 sequential PRs (transport package only → edge-worker integration → hardening guards) if you prefer.

Status

I'd love to upstream this if there's interest. Happy to address whatever review feedback comes. If the rail doesn't fit the project direction, no hard feelings — I'll continue maintaining the fork.

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions