devops
Technical notes on web development, DevOps, and AI integration.
85 articles
- 23:25devops
Done on the tracker is a claim; the proof lives in Git
A card flipped to Done with nothing behind it. Now a work item only counts as done when its evidence hashes actually resolve in the repository.
TL;DR: A Done label means nothing without Git proof, so I stopped trusting tracker status alone. I built a gate that only allows Done when visible evidence lines with commit hashes and tags actually resolve in the repo. Strict rules like WIP limits, date handling, and separating docs keep the board honest and verifiable.
#plane#self-hosting#git - 23:25backend
Two-way document sync: the trustworthy tool refuses precisely
A pull returned different content than my push, with no error. The fix was not a smarter parser but a precise map of what the sync refuses to do.
TL;DR: My Markdown quietly broke after a pull because Plane sanitizes HTML and drops unsupported formatting without warning. Instead of forcing acceptance, I built a sync engine that refuses risky work, storing Markdown safely and using stripped text as the stable identity. It classifies every outcome into six explicit statuses and fails loudly rather than guessing, prioritizing data integrity over convenience.
#plane#self-hosting#sync - 23:25devops
One-door Plane onboarding: logic in one repo, data in every repo
I almost copied a skill folder into every repository. The healthier shape turned out to be one onboarding door and tiny consumer footprints.
TL;DR: Instead of copying the skill to every repo, it stays centralized and consumers keep only light config and templates. This avoids maintenance debt since fixes happen once, while staying cheap because Claude Code only loads skill details when used. An offline helper handles scaffolding safely and idempotently, while online work runs through Plane's MCP tools.
#plane#self-hosting#claude-code - 22:30devops
Live probe vs my Plane spec: two assumptions died in 27 minutes
My 905-line Plane sync spec lost two assumptions to a live probe: HTML markers stripped by server-side sanitization, archive endpoint 404.
TL;DR: A live probe against Plane Community Edition broke two assumptions in a 905-line sync spec before any code was written. Hidden HTML comments for identity were stripped by server sanitization, and the archive endpoint returned 404 because CE trails cloud. Fix stores metadata in visible text and a config file, uses Done instead of archive, and hashes normalized text.
#plane#self-hosting#rest-api - 13:37ai
One small patch in plane-mcp to make it talk to Plane Community Edition
The /dependencies/ route 404s on CE self-hosted. A small fallback to the old /relations/ route fixes it.
TL;DR: The MCP link tool returned a bare 404 though the work items existed, because its cloud route is missing in Community Edition. The patch detects a missing-route 404 versus missing data and falls back to the older CE route with adjusted payloads. This keeps upstream merges simple and is validated by a live smoke test creating and linking real items.
#plane#mcp#ai - 13:21ai
Plane MCP is live: three operational gotchas you only feel after using it
No workspace delete via MCP, CORS reused as CSRF, and an API token sitting in .mcp.json next to the code.
TL;DR: Workspace deletion isn't exposed via API and requires double confirmation in the UI to prevent irreversible wipes by AI agents. CORS and CSRF share the single CORS_ALLOWED_ORIGINS variable, so adding an origin requires recreating all backend containers, not the proxy. API tokens sit in plaintext in .mcp.json, so you must gitignore the file and use expiring tokens.
#plane#mcp#ai - 21:53ai
Plane MCP for self-hosting, no OAuth, just header auth
plane-mcp-server http mode always builds an OAuth app and crashes without env vars. A 16-line wrapper unlocks header auth with live token checks.
TL;DR: HTTP mode crashed at startup because it always built an OAuth app without env vars. A 16-line wrapper instead calls get_header_mcp() to run a stateless server on loopback 8211 that validates Authorization headers against Plane. This lets local AI agents securely use all 30 tools with no internet exposure or full OAuth setup.
#plane#mcp#ai - 21:53devops
A 502 from Cloudflare Tunnel, caused by a compose service name
The tunnel said 502 while every container was green. Compose DNS only knows service names, and a six-line alias solved it in one step.
TL;DR: Public URL returned 502 while everything looked healthy and curl to the proxy worked fine. Ingress pointed to proxy on port 8199 but the service is actually plane-proxy, so Docker DNS had no record and cloudflared couldn't reach the origin. Adding a network alias proxy to plane-proxy fixed it, keeping the name stable for anything outside the repo.
#docker#compose#cloudflared - 21:07devops
Self-hosting Plane without a single public port
A 13-service Plane stack on a single-admin VPS: a cloudflared sidecar as the only public door, bind-mounted state, and no public host ports.
TL;DR: Running Plane on a packed VPS clashed with its default host ports 80 and 443. I kept the 13-service stack fully internal and exposed it only via a Cloudflare Tunnel sidecar aimed at the proxy, not port 8000 which is just the API. This keeps the firewall to SSH only, handles TLS at the edge, and removes public port exposure.
#self-hosting#plane#docker