Skip to content

[bug]: POST /api/v1/workspaces/{slug}/projects/ does not create ProjectIdentifier, crashing web client #8909

@jeremylongshore

Description

@jeremylongshore

Is there an existing issue for this?

  • I have searched the existing issues

Summary

Two serializer side-effects that the web UI create paths run are not run by the equivalent public REST API v1 create paths. The missing rows break downstream queries in the web client.

Both cases follow the same pattern:

Web-UI create path Public v1 API create path Missing side-effect
ProjectSerializer.create() ProjectCreateSerializer.create() ProjectIdentifier.objects.create(...)
IssueViewSet.create() IssueListCreateAPIEndpoint.post() issue_description_version_task.delay(..., is_creating=True)

Divergence 1: project_identifiers row not created

apps/api/plane/api/serializers/project.py:

  • ProjectCreateSerializer.create() (used by POST /api/v1/workspaces/{slug}/projects/) calls Project.objects.create(...) and returns without creating a ProjectIdentifier.
  • ProjectSerializer.create() in the same file creates both:
    project = Project.objects.create(**validated_data, workspace_id=self.context["workspace_id"])
    _ = ProjectIdentifier.objects.create(
        name=project.identifier,
        project=project,
        workspace_id=self.context["workspace_id"],
    )

Verified against a live self-hosted instance:

SELECT p.identifier, p.name, pi.name AS identifier_row
FROM projects p
LEFT JOIN project_identifiers pi ON pi.project_id = p.id
WHERE p.workspace_id = (SELECT id FROM workspaces WHERE slug = '<slug>');
-- For every project created via POST /api/v1/.../projects/, identifier_row is NULL.
-- For projects created via the web UI, identifier_row is populated.

Divergence 2: issue_description_versions row not created

apps/api/plane/app/views/issue/base.py::IssueViewSet.create (web UI path) runs:

issue_description_version_task.delay(
    updated_issue=json.dumps(request.data, cls=DjangoJSONEncoder),
    issue_id=str(serializer.data["id"]),
    user_id=request.user.id,
    is_creating=True,
)

apps/api/plane/api/views/issue.py::IssueListCreateAPIEndpoint.post (public v1 path) does not. Verified in the same instance: every issue created via POST /api/v1/.../issues/ has zero rows in issue_description_versions; every issue created via the web UI has one.

Observed effect

On a self-hosted plane-backend:stable / plane-frontend:stable Docker deployment, after seeding a workspace with ~7 projects and ~23 issues exclusively via the v1 API, the web client returned the generic React error boundary ("Looks like something went wrong!") for the affected workspace. Backfilling both missing row types (running issue_description_version_task(..., is_creating=True) for each issue and inserting the missing project_identifiers rows) restored the UI — verified end-to-end with headless Chromium (Playwright, desktop UA and iPhone mobile UA) loading the affected project's work-items view, rendering all issues, and opening the "+ Add work item" flow without an error boundary.

I'm not claiming the error boundary is solely caused by these missing rows — the client isn't instrumented well enough for me to prove causation from the outside. What's fully confirmed is the divergence: identical data flows produce different database state depending on which create path you use, and the web client assumes the web-UI outcome.

Steps to reproduce

  1. Generate a workspace API key.
  2. POST /api/v1/workspaces/{slug}/projects/ with {"name": "Test", "identifier": "TST"}. Returns 201.
  3. POST /api/v1/workspaces/{slug}/projects/{project_id}/issues/ with {"name": "x", "description_html": "<p>x</p>"}. Returns 201.
  4. Query:
    SELECT
      (SELECT count(*) FROM project_identifiers WHERE project_id = '<project_id>')           AS project_identifier_rows,
      (SELECT count(*) FROM issue_description_versions WHERE issue_id = '<issue_id>')        AS issue_description_version_rows;
    Expected (what web UI would produce): 1, 1. Actual: 0, 0.

Suggested fix

  1. Preferred: mirror the web-UI side effects in the v1 serializers/views — add ProjectIdentifier.objects.create(...) to ProjectCreateSerializer.create() and add the issue_description_version_task.delay(..., is_creating=True) call to IssueListCreateAPIEndpoint.post().
  2. Alternative: move both side effects to post_save signals on Project and Issue so every future create path is covered automatically.

A one-shot management command to backfill both row types on already-affected instances would help anyone who hit this before the fix ships.

Happy to open a PR against apps/api if a maintainer green-lights option 1 (or option 2 if preferred).

Environment

  • Self-hosted Docker Compose: makeplane/plane-backend:stable, plane-frontend:stable, plane-proxy:stable, plane-admin:stable, plane-space:stable (pulled 2026-04)
  • Postgres 15.7 (postgres:15.7-alpine)
  • Redis: Valkey 7.2.11

Jeremy made me do it
-claude

Metadata

Metadata

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