Skip to content

[bug]: Wiki Move page to collection silently fails when Plane AI service is disabled #9385

Description

@joshloredo

Bug Report: Wiki "Move page to collection" silently fails when Plane AI (PI) service is disabled — success toast shown, no request dispatched, page_collections never written

Report date: 2026-07-09
Edition: Plane Commercial Edition (Prime CLI Docker Compose installation)
Version: v2.6.3 (all *-commercial:v2.6.3 images)
Severity: High — silent data-loss-appearance bug (user believes content was organized; it was not), core wiki/collections workflow unusable
Reporter environment: Self-hosted, internal network, single Docker host

Note for triage: this was observed on the Commercial Edition. It is being shared with Community Edition maintainers to check whether the same frontend pattern (unconditional dependency on the PI feature-flags endpoint with a silent-failure mutation path) exists in shared/derived frontend code.

Related issue: #9125 (issue://9125). This is related PI-disabled /pi/api/v1/flags/ fallout, but this report is not a duplicate: it covers the wiki collection move mutation silently not dispatching and showing a false success toast.


1. Summary

With the Plane AI (PI) service scaled to zero and OpenSearch removed — a configuration the official docs describe as supported ("OpenSearch (optional). Full-text search index. Without it, search falls back to Postgres-based search," per developers.plane.so Self-hosting 101) — the wiki UI still renders all collection functionality, but the "Move page to collection" action is a silent no-op:

  • The page picker opens and correctly lists all pages (served by Postgres fallback).
  • The user selects a page and clicks Move.
  • A success toast is displayed.
  • No HTTP request of any kind is dispatched by the frontend for the move (verified in browser dev tools Network tab and server-side API/live/proxy logs).
  • No row is ever written to the page_collections table (verified directly in Postgres).
  • The collection continues to display "No pages yet."

Concurrently, the wiki frontend makes repeated requests to GET /pi/api/v1/flags/?workspace_slug=<slug>&is_guest_user=false, which return 502 Bad Gateway because the PI API service is scaled to zero. The leading hypothesis is that the wiki frontend's feature-flag store never fully initializes when this call fails, and flag-gated mutations (page move) silently short-circuit after the optimistic success toast is triggered.

The licensing/entitlement flags endpoint is healthy and returns MOVE_PAGES: true, so plan entitlement is not the gate.


2. Environment

2.1 Deployment

  • Installed via Prime CLI (Docker Compose method), project name plane
  • Host OS: Linux, Docker Compose
  • Reverse proxy: bundled Caddy (makeplane/proxy-commercial:v2.6.3)
  • Instance served over HTTPS on an internal domain (redacted below as plane.internal.example); clients on internal 10.x network
  • Workspace slug: nothic

2.2 Running containers (docker ps)

NAMES                         IMAGE                                 STATUS
plane-proxy-1                 makeplane/proxy-commercial:v2.6.3     Up
plane-live-1                  makeplane/live-commercial:v2.6.3      Up
plane-space-1                 makeplane/space-commercial:v2.6.3     Up (healthy)
plane-admin-1                 makeplane/admin-commercial:v2.6.3     Up (healthy)
plane-web-1                   makeplane/web-commercial:v2.6.3       Up (healthy)
plane-beat-worker-1           makeplane/backend-commercial:v2.6.3   Up
plane-worker-1                makeplane/backend-commercial:v2.6.3   Up
plane-silo-1                  makeplane/silo-commercial:v2.6.3      Up
plane-automation-consumer-1   makeplane/backend-commercial:v2.6.3   Up
plane-api-1                   makeplane/backend-commercial:v2.6.3   Up
plane-outbox-poller-1         makeplane/backend-commercial:v2.6.3   Up
plane-plane-db-1              postgres:15.7-alpine                  Up
plane-plane-mq-1              rabbitmq:3.13.6-management-alpine     Up
plane-iframely-1              makeplane/iframely:v1.2.0             Up
plane-monitor-1               makeplane/monitor-commercial:v2.6.3   Up
plane-plane-minio-1           minio/minio:latest                    Up
plane-plane-redis-1           valkey/valkey:7.2.11-alpine           Up

Note: no OpenSearch container and no PI (Plane Intelligence) containers are present — see §3.

2.3 Relevant environment variables (from plane-api-1, secrets redacted)

OPENSEARCH_ENABLED=0            # was 1 at first observation; see timeline
OPENSEARCH_URL=http://plane-opensearch:9200
OPENSEARCH_USERNAME=admin
OPENSEARCH_PASSWORD=<redacted>
OPENSEARCH_INDEX_PREFIX=plane
OPENSEARCH_ML_MODEL_ID=

PI_BASE_PATH=/pi
PI_BASE_URL=https://plane.internal.example/pi
PI_INTERNAL_SECRET=<redacted>

SILO_HMAC_SECRET_KEY=<redacted>

In /opt/plane/plane.env, the following were set to disable Plane AI (per the documented replica-count mechanism):

PI_API_REPLICAS=0
PI_BEAT_REPLICAS=0
PI_WORKER_REPLICAS=0
PI_MIGRATOR_REPLICAS=0

2.4 Instance configuration (from instance_configurations table)

No AI enable/disable switch exists. Only legacy/unused LLM defaults are present (LLM_PROVIDER=openai, LLM_MODEL=gpt-4o-mini, LLM_API_KEY= empty). The God-mode/instance-admin UI exposes no AI toggle in v2.6.3.


3. Timeline / configuration history

  1. Instance originally ran the full commercial stack including OpenSearch and the Plane AI (PI) services, with PI configured against a self-hosted internal LLM (gpt-oss-120b) running on the same host.
  2. Severe instance-wide latency/network performance degradation was observed. (Root cause never formally profiled; suspected resource contention between the co-located LLM/OpenSearch/vectorization pipeline and the Plane stack.)
  3. To resolve the latency, the operator:
    • Set PI_API_REPLICAS, PI_BEAT_REPLICAS, PI_WORKER_REPLICAS, PI_MIGRATOR_REPLICAS to 0 in plane.env
    • Removed the OpenSearch container
    • (Latency resolved.)
  4. At this point OPENSEARCH_ENABLED was still 1. The backend worker logged continuous connection failures (see §5.1) attempting to reach the removed OpenSearch host, and wiki list endpoints backed by the search index returned empty result sets.
  5. Operator set OPENSEARCH_ENABLED=0 in /opt/plane/plane.env and restarted via Prime CLI. Confirmed the API container picked up the new value.
  6. After this change: the OpenSearch tracebacks stopped, the search fallback began working (page picker now lists pages; the backend task logs 'OpenSearch disabled' and no longer errors), but the Move action remains a silent no-op — the bug reported here.

4. Steps to reproduce

Preconditions: Commercial v2.6.3; PI replicas at 0 (no PI containers); no OpenSearch container; OPENSEARCH_ENABLED=0.

  1. In the wiki, create a page (e.g. "Test Page"). Page creation, editing, versioning all work normally (verified: page_transaction and page_version Celery tasks succeed; page rows present in Postgres pages table).
  2. Create a collection (e.g. "Plane Guides"). Collection creation works (row present in collections table).
  3. Open the collection → "Add existing page" (page picker).
  4. Observe the picker correctly lists all existing pages (Postgres fallback path for GET /api/workspaces/<slug>/collections/<id>/pages-search/ works — returns 200 with populated results).
  5. Select a page, click Move.
  6. Observe a success toast appears.

Expected: page appears in the collection; a row is written to page_collections.

Actual: collection still shows "No pages yet"; no row in page_collections; no write request was ever sent to the backend.

Reproduces 100% of the time, across multiple pages, in both Chrome (v150) and the Plane desktop app (Electron).


5. Evidence collected

5.1 Backend logs — before OPENSEARCH_ENABLED=0 (context)

Worker repeatedly failed OpenSearch bulk indexing after the container was removed (name resolution failure, as expected):

opensearchpy.exceptions.ConnectionError: ConnectionError(
  HTTPSConnection(host='plane-opensearch', port=9200):
  Failed to resolve 'plane-opensearch' ([Errno -2] Name does not resolve))
...
"Error processing Issue updates with action index: ConnectionError(...)"
"OpenSearch batch processed 0 Issue instances with registry-powered cascades"

Originating task: plane.ee.bgtasks.batched_search_update_task.process_batched_opensearch_updates, via plane/ee/documents/core/registry.py::process_updates_group_with_registrydjango_opensearch_dsl.

5.2 Backend logs — after OPENSEARCH_ENABLED=0

Indexing task now exits cleanly:

Task plane.ee.bgtasks.batched_search_update_task.process_batched_opensearch_updates[...]
succeeded in 0.0002s: 'OpenSearch disabled'

Page write paths healthy during repro (page_transaction, page_version succeed). API request log during a full Move repro shows only reads — representative excerpt:

GET /api/workspaces/nothic/collections/ 200
GET /api/workspaces/nothic/collections/1d58343f-b9ee-4687-bfa9-27916f4ec1d9/pages/?per_page=50 200   (response_size: 232 ≈ empty result envelope)
GET /api/workspaces/nothic/collections/1d58343f-b9ee-4687-bfa9-27916f4ec1d9/pages-search/ 200        (response_size: 433 — pages listed, picker populated)

No POST/PATCH/PUT/DELETE touching any collection or page-membership endpoint appears in api, live, or proxy logs at the moment Move is clicked. (Logs were tailed live with docker compose -p plane logs -f api live proxy during repro; the only writes observed in the window were unrelated page-description autosave PATCHes from the editor.)

5.3 Browser evidence (Chrome dev tools, recording from before click)

  • Network (Fetch/XHR): at the moment Move is clicked, no request fires. Only unrelated polling (notifications/unread/, stickies/, pages/?type=...) is visible.
  • Network (WS): no WebSocket frames are sent on Move. (On the collections view no live/collab socket is open at all; the collab socket belongs to the page editor.)
  • Console: no errors or warnings at the moment of the click.
  • The success toast still renders — i.e., the toast is fired optimistically and is not tied to any completed (or even attempted) network operation.

5.4 Feature-flag requests observed on wiki page load

Filtering the Network tab for "flag" during a wiki load shows:

  1. GET https://plane.internal.example/api/payments/workspaces/nothic/flags/200 OK (Django API / entitlement flags). Response includes, among many others:

    "MOVE_PAGES": true,
    "LINK_PAGES": true,
    "NESTED_PAGES": true,
    "PAGE_COMMENTS": true,
    "PAGE_DOCX_EXPORT": true,
    "PAGE_ISSUE_EMBEDS": true,
    "AI_ACTIONS": true, "AI_CHAT": true, "AI_CONVERSE": true, "AI_DEDUPE": true,
    "ADVANCED_SEARCH": true,
    ...
    

    → Plan entitlement is NOT the gate; MOVE_PAGES is licensed and true.

  2. GET https://plane.internal.example/pi/api/v1/flags/?workspace_slug=nothic&is_guest_user=false502 Bad Gateway (Content-Length: 0, Server: Caddy, Via: 1.1 Caddy). Fired (at least) twice per wiki load, fails every time — expected at the transport level, since PI_API_REPLICAS=0 means nothing is listening behind Caddy's /pi route.

Frontend bundles loaded on the same view include with-feature-flag-hoc-*.js, use-ai-flag-*.js, feature-flags-*.js, with-ai-feature-flag-hoc-*.js, use-editor-flagging-*.js — i.e., the wiki UI is wrapped in feature-flag HOCs whose flag source includes the failing PI endpoint.

5.5 Database evidence (Postgres, read-only)

Pages exist and persist correctly:

SELECT id, name, access, created_at FROM pages ORDER BY created_at DESC LIMIT 10;
-- 5 rows, including the pages used in repro (access = 0 for all)

Collection exists:

SELECT id, name, created_at FROM collections;
-- 1 row: 1d58343f-b9ee-4687-bfa9-27916f4ec1d9 | Plane Guides

Membership table has never been written:

SELECT * FROM page_collections ORDER BY created_at DESC LIMIT 10;
-- (0 rows)

(Schema note: membership table is page_collections with columns created_at, updated_at, deleted_at, id, collection_id, created_by_id, page_id, updated_by_id, workspace_id, sort_order. A sibling collection_members table also exists.)


6. Analysis / hypothesis

  1. The failure is client-side dispatch, not backend rejection. Zero network activity on Move (no XHR, no WS frame, no console error), across two clients, with server logs corroborating. The backend is never asked to create the membership.
  2. Entitlement flags are ruled out. /api/payments/.../flags/ returns 200 with MOVE_PAGES: true.
  3. The only failing dependency the wiki loads is the PI flag endpoint (/pi/api/v1/flags/ → 502). Given the wiki components are wrapped in feature-flag / AI-flag HOCs (see bundle names in §5.4), the working hypothesis is:
    • the wiki frontend's flag store depends (fully or partially) on the PI flags response;
    • when it fails, the store never reaches a state in which the Move mutation executes;
    • the mutation path short-circuits after the optimistic success toast, producing a silent no-op with false success feedback.
  4. Docs vs. behavior mismatch. Official docs state OpenSearch is optional with Postgres fallback, and describe scaling PI replicas as the AI on/off mechanism. In practice:
    • The Postgres search fallback works only after OPENSEARCH_ENABLED=0 is also set (arguably fine).
    • But there is no supported way to tell the frontend that PI is absent: no env switch, no God-mode toggle, no instance_configuration key. The frontend calls /pi/ unconditionally, and degrades silently rather than visibly when it 502s.
  5. Secondary defect regardless of root cause: a success toast must not fire for an operation that was never dispatched. Even if PI-off is deemed unsupported, the correct behavior is an error state, not false success.

7. Suggested areas to inspect (for maintainers)

  • Wiki/pages frontend: the Move/add-to-collection mutation and its interaction with the feature-flag store (files corresponding to with-feature-flag-hoc, use-ai-flag, with-ai-feature-flag-hoc, feature-flags bundles); whether flag-store initialization failure (PI flags fetch rejected) leaves gated mutations in a state where handlers return early after the toast.
  • Whether the PI flags fetch has any error handling / defaulting path when the service is unreachable (502/timeout), and whether wiki collection mutations should be gated on PI flags at all (MOVE_PAGES is an entitlement flag served by the main API).
  • Toast ordering: success feedback fired before/without awaiting the mutation promise.
  • Whether an official "AI disabled" instance state should exist that (a) stops the frontend calling /pi/*, and (b) is exposed via env or instance admin.

8. Workarounds attempted / status

  • OPENSEARCH_ENABLED=0: fixed background indexing errors and the empty page picker; did not fix Move.
  • No God-mode AI toggle exists in v2.6.3; no PI_* enable/disable env var exists (only base path/URL/secret and replica counts).
  • Restoring OpenSearch + PI API (memory-capped) is expected to restore functionality but was deliberately deferred pending this report, since the failing configuration is documented as supported.
  • No alternate UI path tested successfully yet (page-side "move to collection" menu / create-page-inside-collection were proposed but not yet confirmed either way at time of writing).

9. Redactions

Domain, OpenSearch password, PI internal secret, and Silo HMAC secret are redacted. Workspace slug, page/collection UUIDs, and timestamps are real and internally consistent across the logs, browser captures, and DB queries above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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