Skip to content

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

Open
SandipBajracharya wants to merge 3 commits into
mainfrom
OUT-4029
Open

OUT-4029 | Scope task/label deletion by id + workspace (fix cross-workspace collateral deletes)#1399
SandipBajracharya wants to merge 3 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

SandipBajracharya and others added 3 commits August 12, 2026 14:44
… 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
@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 12, 2026 9:05am

Request Review

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown

Greptile Summary

The PR prevents task deletion from affecting other workspaces and preserves the global label registry’s monotonic counters.

  • Recursive subtask deletion now selects task IDs and scopes the soft-delete by both ID and workspace.
  • Assignee and individual-task deletion paths no longer remove label-registry rows.
  • Unit and PostgreSQL integration coverage exercises colliding labels across workspaces.
  • Jest configuration transpiles the pure-ESM retry dependencies, and the authentication assertion is aligned with the current public-route source.

Confidence Score: 5/5

The PR appears safe to merge, with deletion correctly constrained to task IDs in the acting workspace and label counters preserved.

The changed recursive deletion retains the existing Prisma soft-delete behavior, all production callers first delete the parent and then select its live ltree descendants, and the resulting update is constrained by both descendant IDs and workspace ID; no actionable regression remains.

Important Files Changed

Filename Overview
src/app/api/tasks/subtasks.service.ts Replaces label-keyed recursive deletion with workspace-scoped task-ID deletion while preserving soft-delete semantics.
src/app/api/tasks/tasks.service.ts Removes label-registry cleanup from task reassignment, task deletion, and assignee deletion paths.
src/app/api/tasks/public/public.service.ts Aligns public task reassignment and deletion with the retained label-registry behavior.
src/app/api/label-mapping/label-mapping.service.ts Removes the label deletion API so registry rows remain available as monotonic sequence state.
src/app/api/tasks/subtasks.service.integration.test.ts Adds real-PostgreSQL coverage proving that a colliding label in another workspace survives subtree deletion.
next.config.js Transpiles ESM retry packages for Next/Jest and permits the additional ngrok development domain.

Sequence Diagram

sequenceDiagram
  participant Caller as Task deletion caller
  participant DB as PostgreSQL / Prisma
  participant Subtasks as SubtaskService
  Caller->>DB: Soft-delete parent in workspace
  Caller->>Subtasks: softDeleteAllSubtasks(parentId)
  Subtasks->>DB: Select live descendant IDs by ltree path + workspaceId
  DB-->>Subtasks: Descendant task IDs
  Subtasks->>DB: deleteMany by IDs + workspaceId
  Note over DB: Prisma extension converts deleteMany to deletedAt update
  Note over DB: Labels registry remains unchanged
Loading

Reviews (1): Last reviewed commit: "OUT-4029 | Fix authenticate test suite (..." | 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.

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