Skip to content

OUT-4029 | Scope task/label deletion by id + workspace (fix cross-workspace collateral deletes) - #1399

Merged
SandipBajracharya merged 4 commits into
mainfrom
OUT-4029
Aug 26, 2026
Merged

OUT-4029 | Scope task/label deletion by id + workspace (fix cross-workspace collateral deletes)#1399
SandipBajracharya merged 4 commits into
mainfrom
OUT-4029

Conversation

@SandipBajracharya

Copy link
Copy Markdown
Collaborator

What & why

SubtaskService.softDeleteAllSubtasks (and deleteAllAssigneeTasks) deleted DB rows keyed on the task label string with no workspaceId filter. Labels has no workspaceId column and prefixes derive from brand/company names, so strings like THE10-001 collide across workspaces — deleting a task in one workspace soft-deleted tasks (and Labels rows) in other workspaces.

Prod impact (read-only queries, 2026-08-06): 2,390 tasks soft-deleted as collateral across 263 workspaces; worst offender THE10-001 spanned 121 workspaces.

Linear: OUT-4029

Changes

Core fix

  • softDeleteAllSubtasks: select id (not label) from the ltree query and deleteMany by id + workspaceId.
  • deleteAllAssigneeTasks: drop the unscoped label.deleteMany.
  • Remove LabelMappingService.deleteLabel and all call sites. Labels is a next-number registry — orphan rows are harmless, and deleting them rolls the counter back and causes duplicate label reissue. This also makes the OUT-4027 null-guard (OUT-4027 | Fix 500 when deleting a task whose label row is missing #1397) moot.

Tests

  • Unit: both delete methods delete by id/assignee scoped to workspaceId and never touch Labels.
  • Integration (Testcontainers Postgres): two workspaces sharing THE10-001; deleting one workspace's tree soft-deletes only its own subtree and leaves the other untouched. Verified to fail against the old string-keyed code and pass with the fix.

Test infra (incidental)

  • next.config.js: transpilePackages: ['p-retry', 'is-network-error'] so next/jest can load these pure-ESM deps (fixes a pre-existing authenticate.test.ts load failure). Also carries a ngrok dev-origin line.
  • authenticate.test.ts: corrected a stale public-route source assertion (publicplatform) that matched an intentional earlier change (eb76729f), previously hidden while the suite couldn't load.

Not included / follow-ups

  • Restoring the 2,390 collaterally-deleted tasks — they're soft-deleted and recoverable; needs a src/cmd/ script + product sign-off (per the ticket's open question).
  • The Labels table now accumulates orphan rows on delete/reassign (agreed tradeoff) — worth a follow-up if the table grows large.

Verification

  • yarn tsc clean, yarn lint:check clean.
  • Unit tests green; integration test green (and proven to catch the regression).

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

OUT-4029

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tasks-app Ready Ready Preview Aug 26, 2026 8:50am

Request Review

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown

Greptile Summary

The PR removes generated task labels and scopes recursive and assignee deletion operations to task IDs and the authenticated workspace.

  • Replaces label-based descendant deletion with workspace-scoped task-ID deletion.
  • Removes the label registry, label generation, and label cleanup paths.
  • Preserves the public API’s label field while adapting private UI and response types.
  • Adds unit and PostgreSQL integration coverage for cross-workspace deletion isolation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/app/api/tasks/subtasks.service.ts Replaces collision-prone label deletion with descendant-ID deletion constrained to the authenticated workspace.
src/app/api/tasks/tasks.service.ts Removes label generation and registry cleanup while retaining workspace-scoped assignee task deletion.
src/app/api/tasks/public/public.service.ts Stops generating and reassigning labels while preserving the public task lifecycle.
prisma/migrations/20260825000000_drop_task_label_and_labels_table/migration.sql Adds the empty task-label default and removes the obsolete Labels registry table.
prisma/schema/task.prisma Keeps label as a non-null compatibility field with an empty-string default.
src/types/dto/tasks.dto.ts Removes label from private task and ancestor response types while the public contract remains separately preserved.
src/app/api/tasks/subtasks.service.integration.test.ts Verifies that recursive deletion does not affect a task tree in another workspace.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Delete task in workspace] --> B[Select live descendant IDs by ltree path and workspace]
    B --> C[Soft-delete matching IDs with workspace scope]
    C --> D[Preserve tasks in other workspaces]
    E[Create new task] --> F[Database supplies empty label default]
    F --> G[Public serializer retains label field]
Loading

Reviews (3): Last reviewed commit: "OUT-4029 | Stop generating task labels; ..." | Re-trigger Greptile

@priosshrsth priosshrsth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@SandipBajracharya Let's not create label too.

@vercel

vercel Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Deployment failed for project tasks-app with the following error:

Deploying Serverless Functions to multiple regions is restricted to the Pro and Enterprise plans.

Learn More: https://vercel.link/multiple-function-regions

@SandipBajracharya

Copy link
Copy Markdown
Collaborator Author

@greptileai review again

priosshrsth
priosshrsth previously approved these changes Aug 26, 2026

@priosshrsth priosshrsth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

SandipBajracharya and others added 4 commits August 26, 2026 14:34
… Labels rows

softDeleteAllSubtasks and deleteAllAssigneeTasks deleted rows keyed on the task
label string with no workspaceId filter. Labels has no workspaceId and prefixes
derive from brand/company names, so strings like THE10-001 collide across
workspaces and deletes wiped other workspaces' tasks.

- softDeleteAllSubtasks: select id (not label) and deleteMany by id + workspaceId
- deleteAllAssigneeTasks: drop the unscoped label.deleteMany
- Remove LabelMappingService.deleteLabel and its call sites; Labels is a
  next-number registry where orphan rows are harmless and deleting them rolls
  the counter back, causing duplicate label reissue

Co-Authored-By: Claude Opus 4.8 <[email protected]>
- Unit: softDeleteAllSubtasks and deleteAllAssigneeTasks delete by id/assignee
  scoped to workspaceId and never touch Labels rows
- Integration: two workspaces sharing a label string; deleting one workspace's
  tree soft-deletes only its own subtree and leaves the other untouched
- seedTask: optional label so integration tests can seed colliding labels

Co-Authored-By: Claude Opus 4.8 <[email protected]>
authenticate.test.ts failed to load: p-retry and is-network-error are pure ESM
and next/jest only transforms packages listed in transpilePackages.

- next.config.js: transpile p-retry / is-network-error (also adds ngrok dev origin)
- authenticate.test.ts: update the public-route source fallback assertion from
  "public" to "platform" to match the intentional change in commit eb76729
  (was hidden while the suite couldn't load)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…tibility

Stops generating per-task label codes and deletes LabelMappingService and the
Labels registry table. Keeps the Tasks.label column (now @default('')) so the
public automation API stays backward compatible: pre-existing tasks keep their
codes, new tasks return ''.

- Public API: label retained in PublicTaskDtoSchema + serializer, with a
  serializer test locking the contract (empty for new, preserved for old).
- Internal app no longer uses label — drag preview shows the workflow-state
  icon, the breadcrumb shows the task title, the search filter drops the label
  matcher.
- Breadcrumb: desktop truncates the title at 25 chars; below 600px it uses a
  shorter 12-char title to avoid the platform header overflowing.
- Migration (edited in place): set Tasks.label DEFAULT '' and DROP TABLE Labels
  (the column is kept, not dropped).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@SandipBajracharya

Copy link
Copy Markdown
Collaborator Author

@greptileai review for the last time

@SandipBajracharya
SandipBajracharya merged commit 1bc04dd into main Aug 26, 2026
4 checks passed
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.

2 participants