user-project move detached multi-project issues (unscoped UPDATE) — fixed #17

Closed
opened 2026-05-17 16:36:13 +03:00 by oleks · 3 comments
Owner

Severity: high.

Endpoint: POST /api/v1/users/{username}/projects/{id}/issues/{issue_id}/move

Observed: when an issue is a card on multiple user-level projects, calling move (to position it in a column) on ONE project removes that issue from ALL OTHER user projects it belonged to. Expected: the move affects only the named project; membership and column position in other projects are untouched.

Repro: add issue internal id 123 to user projects 9, 10, 11 (different columns). Call move on project 10. Result: issue 123 disappears from projects 9 and 11.

Impact: breaks multi-project membership for an issue. Concretely blocks the anxious plugin's steward three-board model (separate State/Domain/Activity user projects) — cross-referenced from an issue on oleks/claude-plugin-anxious.

Suggested: the move handler should scope its mutation to the target project only, not clear other project associations.

**Severity**: high. **Endpoint**: `POST /api/v1/users/{username}/projects/{id}/issues/{issue_id}/move` **Observed**: when an issue is a card on multiple user-level projects, calling `move` (to position it in a column) on ONE project removes that issue from ALL OTHER user projects it belonged to. Expected: the move affects only the named project; membership and column position in other projects are untouched. **Repro**: add issue internal id 123 to user projects 9, 10, 11 (different columns). Call move on project 10. Result: issue 123 disappears from projects 9 and 11. **Impact**: breaks multi-project membership for an issue. Concretely blocks the `anxious` plugin's steward three-board model (separate State/Domain/Activity user projects) — cross-referenced from an issue on `oleks/claude-plugin-anxious`. **Suggested**: the move handler should scope its mutation to the target project only, not clear other project associations.
oleks closed this issue 2026-05-17 17:06:19 +03:00
oleks changed title from user-project move endpoint detaches the issue from all other projects to [invalid — source review] user-project move is project-scoped; does NOT detach issue from other projects 2026-05-17 18:05:43 +03:00
oleks reopened this issue 2026-05-17 18:05:43 +03:00
Author
Owner

Correction — this is not reproducible as filed; the source contradicts the original report.

Reviewed the deployed fork (commit 1cd81ff925):

  • project_issue join is composite-unique on (IssueID, ProjectID) — models/migrations/v1_22/v294.go. Multiple projects per issue is supported by design.
  • models/issues/issue_project.go is multi-project throughout: LoadProjects(), projectIDs(), ProjectColumnMap() (per-project column); IssueAssignOrRemoveProject(newProjectIDs []int64) does a set-diff (removes only old−new), never a blanket clear.
  • The dedicated move endpoint MoveUserProjectIssueservices/projects/issue.go:25 MoveIssuesOnProjectColumn is strictly scoped to column.ProjectID (uses ProjectColumnMap), requires prior membership (ErrIssueNotInProject otherwise), and deletes no other project_issue rows.

The "move removes the issue from all other projects" behaviour is not present in the move path. The detachment observed during board repair traces to the anxious steward Defect-2 destructive prune (it deliberately removed those cards on a phantom "repos left oleks/*"), fixed in anxious 0.7.2 — not to this endpoint. Most likely the repair tooling hit ErrIssueNotInProject (cards were added to the default column, not yet the target) and fell back to add/remove semantics rather than the dedicated /move.

Recommendation: close as not a bug / invalid. If a multi-project detachment is ever reproduced using the dedicated POST /users/{username}/projects/{id}/issues/{issue_id}/move endpoint specifically (with the issue already a member of that project), reopen with the exact calls.

**Correction — this is not reproducible as filed; the source contradicts the original report.** Reviewed the deployed fork (commit `1cd81ff925`): - `project_issue` join is composite-unique on (IssueID, ProjectID) — `models/migrations/v1_22/v294.go`. Multiple projects per issue is supported by design. - `models/issues/issue_project.go` is multi-project throughout: `LoadProjects()`, `projectIDs()`, `ProjectColumnMap()` (per-project column); `IssueAssignOrRemoveProject(newProjectIDs []int64)` does a set-diff (removes only old−new), never a blanket clear. - The dedicated move endpoint `MoveUserProjectIssue` → `services/projects/issue.go:25 MoveIssuesOnProjectColumn` is strictly scoped to `column.ProjectID` (uses `ProjectColumnMap`), requires prior membership (`ErrIssueNotInProject` otherwise), and deletes no other `project_issue` rows. The "move removes the issue from all other projects" behaviour is not present in the move path. The detachment observed during board repair traces to the `anxious` steward Defect-2 destructive prune (it deliberately removed those cards on a phantom "repos left oleks/*"), fixed in anxious 0.7.2 — not to this endpoint. Most likely the repair tooling hit `ErrIssueNotInProject` (cards were added to the default column, not yet the target) and fell back to add/remove semantics rather than the dedicated `/move`. Recommendation: close as **not a bug / invalid**. If a multi-project detachment is ever reproduced using the dedicated `POST /users/{username}/projects/{id}/issues/{issue_id}/move` endpoint specifically (with the issue already a member of that project), reopen with the exact calls.
Author
Owner

Re the source-review correction: I ran an empirical test against the exact reviewed commit (1cd81ff925, pre-fix) and the bug is reproducible via the dedicated /move endpoint. Sharing the proof so this can be settled on evidence.

Test: TestAPIUserProjects/MoveProjectIssueMultiProjectIsolation (integration, sqlite) — issue assigned to two user projects p1 and p2, positioned in a column of each, then POST /api/v1/users/{user}/projects/{p1}/issues/{issue}/move to a p1 column. Asserts p2's column is unchanged.

Result on 1cd81ff925 (pre-fix):

--- FAIL: TestAPIUserProjects/MoveProjectIssueMultiProjectIsolation
    api_user_project_test.go:541:
        Error: Not equal: expected: 12  actual: 11
        Messages: issue must remain in its original column in other projects

p2's project_issue.project_board_id was rewritten from p2's column (id 12) to p1's column (id 11). The same test PASSES on the fixed commit.

Why the review concluded otherwise: the review is correct that the move path deletes no project_issue rows and that ProjectColumnMap() is per-project. But the actual mutation on the pre-fix line was a raw db.Exec("UPDATE project_issue SET project_board_id=?, sorting=? WHERE issue_id=?", …) — scoped to issue_id only, no project_id predicate. ProjectColumnMap() is consulted only for the timeline-comment branch, not for this UPDATE. So no row is deleted, but every project's row for that issue has its project_board_id overwritten to the target project's column. The issue keeps its p2 membership row, but that row now points at a column belonging to p1, so it renders in no p2 column and effectively "disappears" from p2's board — exactly the reported symptom.

Corroboration: upstream/main independently carries the identical fix, with the rationale comment "The WHERE clause must include both issue_id AND project_id … so moving an issue's column in one project doesn't affect its column in other projects when the issue is assigned to multiple projects." The fork's commit 1011241a67 regressed it back to the unscoped form.

Both causes were real. The anxious steward Defect-2 destructive prune (fixed in anxious 0.7.2) was almost certainly the primary cause of the observed board damage during repair. This endpoint bug is a separate, latent defect that also produces the same symptom whenever a multi-project issue is moved via /move. It is fixed in PR #18 (merged ad46f6c) and is now live on git.oleks.space as 1.26.0-unstable-2026-05-17, with the regression test above guarding it.

Recommendation: this is fixed, not invalid. Closing as fixed (rather than invalid) keeps the regression test's intent documented. Happy to defer to your call on the final state/title.

Re the source-review correction: I ran an empirical test against the exact reviewed commit (`1cd81ff925`, pre-fix) and the bug **is** reproducible via the dedicated `/move` endpoint. Sharing the proof so this can be settled on evidence. **Test:** `TestAPIUserProjects/MoveProjectIssueMultiProjectIsolation` (integration, sqlite) — issue assigned to two user projects p1 and p2, positioned in a column of each, then `POST /api/v1/users/{user}/projects/{p1}/issues/{issue}/move` to a p1 column. Asserts p2's column is unchanged. Result on `1cd81ff925` (pre-fix): ``` --- FAIL: TestAPIUserProjects/MoveProjectIssueMultiProjectIsolation api_user_project_test.go:541: Error: Not equal: expected: 12 actual: 11 Messages: issue must remain in its original column in other projects ``` p2's `project_issue.project_board_id` was rewritten from p2's column (id 12) to **p1's column (id 11)**. The same test PASSES on the fixed commit. **Why the review concluded otherwise:** the review is correct that the move path *deletes no* `project_issue` rows and that `ProjectColumnMap()` is per-project. But the actual mutation on the pre-fix line was a raw `db.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", …)` — scoped to `issue_id` only, no `project_id` predicate. `ProjectColumnMap()` is consulted only for the timeline-comment branch, not for this UPDATE. So no row is deleted, but every project's row for that issue has its `project_board_id` overwritten to the target project's column. The issue keeps its p2 *membership* row, but that row now points at a column belonging to p1, so it renders in no p2 column and effectively "disappears" from p2's board — exactly the reported symptom. Corroboration: `upstream/main` independently carries the identical fix, with the rationale comment *"The WHERE clause must include both issue_id AND project_id … so moving an issue's column in one project doesn't affect its column in other projects when the issue is assigned to multiple projects."* The fork's commit `1011241a67` regressed it back to the unscoped form. **Both causes were real.** The `anxious` steward Defect-2 destructive prune (fixed in anxious 0.7.2) was almost certainly the primary cause of the *observed* board damage during repair. This endpoint bug is a separate, latent defect that also produces the same symptom whenever a multi-project issue is moved via `/move`. It is fixed in PR #18 (merged `ad46f6c`) and is now live on git.oleks.space as `1.26.0-unstable-2026-05-17`, with the regression test above guarding it. Recommendation: this is **fixed**, not invalid. Closing as fixed (rather than invalid) keeps the regression test's intent documented. Happy to defer to your call on the final state/title.
Author
Owner

Resolved. Empirically confirmed the bug was real and reproducible via the dedicated POST /api/v1/users/{username}/projects/{id}/issues/{issue_id}/move endpoint on the reviewed commit 1cd81ff925 (regression test TestAPIUserProjects/MoveProjectIssueMultiProjectIsolation FAILS pre-fix: p2's project_board_id overwritten 12→11; PASSES on the fix).

Root cause: raw db.Exec("UPDATE project_issue SET project_board_id=?, sorting=? WHERE issue_id=?", …) scoped to issue_id only — every project's row for the issue was rewritten to the target project's column. Fixed by scoping the UPDATE to (issue_id, project_id) (matches upstream/main's identical fix; the fork's 1011241a67 had regressed it).

Fixed in PR #18 (merged ad46f6c), live on git.oleks.space as 1.26.0-unstable-2026-05-17, guarded by the regression test. Closing as fixed. (The anxious steward Defect-2 destructive prune, fixed in anxious 0.7.2, was a separate and likely primary cause of the originally observed board damage — both were real.)

Resolved. Empirically confirmed the bug was real and reproducible via the dedicated `POST /api/v1/users/{username}/projects/{id}/issues/{issue_id}/move` endpoint on the reviewed commit `1cd81ff925` (regression test `TestAPIUserProjects/MoveProjectIssueMultiProjectIsolation` FAILS pre-fix: p2's `project_board_id` overwritten 12→11; PASSES on the fix). Root cause: raw `db.Exec("UPDATE project_issue SET project_board_id=?, sorting=? WHERE issue_id=?", …)` scoped to `issue_id` only — every project's row for the issue was rewritten to the target project's column. Fixed by scoping the UPDATE to `(issue_id, project_id)` (matches upstream/main's identical fix; the fork's `1011241a67` had regressed it). Fixed in PR #18 (merged `ad46f6c`), live on git.oleks.space as `1.26.0-unstable-2026-05-17`, guarded by the regression test. Closing as fixed. (The `anxious` steward Defect-2 destructive prune, fixed in anxious 0.7.2, was a separate and likely primary cause of the originally observed board damage — both were real.)
oleks changed title from [invalid — source review] user-project move is project-scoped; does NOT detach issue from other projects to user-project move detached multi-project issues (unscoped UPDATE) — fixed 2026-05-17 18:45:51 +03:00
oleks closed this issue 2026-05-17 18:45:51 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: oleks/gitea#17