Skip to content

fix(git): stop invisible connections from blocking provider deletion - #553

Merged
dviejokfs merged 2 commits into
mainfrom
fix/git-provider-delete-invisible-connections
Aug 5, 2026
Merged

fix(git): stop invisible connections from blocking provider deletion#553
dviejokfs merged 2 commits into
mainfrom
fix/git-provider-delete-invisible-connections

Conversation

@dviejokfs

Copy link
Copy Markdown
Contributor

Problem

Deleting a git provider failed with "Cannot delete provider GitLab because it has 1 connection(s)" while the same page rendered "No connections found" — a dead end with nothing to act on.

Root cause is a mismatch between two queries:

  • The provider detail page listed connections from the per-user endpoint (get_user_connections_paginated, user_id = caller + is_active = true) and filtered client-side by provider.
  • delete_provider counted every active connection of the provider, regardless of owner.

So any connection owned by another user — or with user_id = NULL — made the provider permanently undeletable and permanently un-findable. Connection deletes hit the same wall: the blocking row was invisible, and the UI's onError threw away the server's explanation.

Fix

  • delete_provider now blocks only on projects that actually deploy from the provider (naming them), and cascades the provider's connections + repositories — the same semantics as the existing /safe-delete route.
  • Deletion checks use a new get_all_provider_connections (no owner/active filter), so a deactivated connection used by a project is no longer skipped by the safety check while still being cascaded away.
  • GET /git-providers/{id}/connections returns all of the provider's connections, and the detail page uses it instead of the per-user list — the list can no longer contradict the delete error. Inactive rows already render an "Inactive" badge.
  • delete_connection names the blocking projects; the UI now shows the Problem Details detail instead of a bare "Failed to delete connection".
  • Delete dialog states what will be cascaded instead of the now-wrong "remove connections first".

Evidence

New regression test delete_provider_removes_connections_the_caller_cannot_see seeds a provider with an ownerless active connection + a deactivated one and deletes it.

Against the old guard it reproduces the reported error verbatim:

```
provider with no project usage should delete: InvalidConfiguration("Cannot delete provider GitLab because it has 1 connection(s)")
test result: FAILED. 0 passed; 1 failed
```

With the fix:

```
$ cargo test -p temps-git --lib
test result: ok. 299 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

```
$ cargo clippy -p temps-git --all-targets -- -D warnings
Finished dev profile ... (no warnings)

$ bunx tsc --noEmit -p tsconfig.json # web/
(clean)
```

Note: one earlier full-suite run failed at TestDatabase::with_migrations() (testcontainer startup), green on rerun — unrelated to this change.

No API shape change, so no SDK regen or CLI parity work is required.

A git provider could not be deleted whenever a connection row existed for
it, even when that row was invisible to the caller: the provider detail
page lists connections from the per-user endpoint (user_id = caller) and
filters to active ones, while the delete guard counted every active
connection for the provider regardless of owner. The result was a dead
end — "Cannot delete provider GitLab because it has 1 connection(s)" next
to "No connections found", with no way to reach the offending row.

- delete_provider now blocks only on projects that actually deploy from
  the provider (naming them), and cascades its connections + repositories,
  matching the existing safe-delete path.
- Deletion checks read every connection, including deactivated ones, so a
  hidden row can no longer be left orphaned by the cascade.
- GET /git-providers/{id}/connections returns all of the provider's
  connections, so the list can no longer contradict the delete error.
- delete_connection names the blocking projects, and the UI surfaces the
  server's detail instead of a bare "Failed to delete connection".
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

📓 Changelog preview

This is what your commits will add to the generated CHANGELOG.md at release time (via git-cliff). Do not edit CHANGELOG.md by hand — it is generated from your Conventional Commit messages.

## [Unreleased]

### Fixed

- **git:** Stop invisible connections from blocking provider deletion
- **git:** Surface connection deletion for every provider type

Follow-up from verifying the previous commit against a real GitLab PAT
provider in the UI. Two dead ends were left:

- "Delete connection" only rendered for GitHub App providers, so a
  PAT/OAuth connection could not be removed at all — including the ones
  that block deleting their provider. DELETE /git-connections/{id} is
  generic and the server-side project guard still applies, so show it for
  every provider type.
- Naming the blocking projects loaded whole project models, which fails
  with "Database Error: unexpected value for Preset enum" if any of them
  carries a column value this build's enums can't decode — replacing the
  actionable reason with an opaque one. Select id/name/slug only.
@dviejokfs

Copy link
Copy Markdown
Contributor Author

UI verification (GitLab PAT provider, slot 11)

Ran the real console against a seeded GitLab PAT provider with an ownerless connection (user_id = NULL) — the exact reported state. Provider creation was seeded directly because create_gitlab_pat_provider synchronously probes {base_url}/api/v4/user and needs a valid token; everything below is the actual UI.

1. Visibility (the contradiction) — provider page now lists the connection, while the per-user endpoint it used to read still returns nothing:

page: "Connections 1 / orphan-gitlab-account / User / Active"
fetch('/api/git-connections') -> {"perUser_total":0,"perUser":[]}

2. Delete with no project usage — dialog states the cascade, delete succeeds, DB confirms:

"Are you sure you want to delete "GitLab"? ... Its 1 connection(s) and their synced
 repositories are deleted with it. Projects still deployed from this provider block
 the delete — the error names them."
-> "Git provider deleted successfully", redirected to /git-providers
select count(*) from git_providers;             -> 0
select count(*) from git_provider_connections;  -> 0

3. Delete blocked by a project — actionable, names it:

Invalid Configuration
Cannot delete provider 'GitLab' because it is used by 1 project(s): 'checkout-api' (ID: 1)

4. Connection delete — blocked, then succeeds once the project is unlinked:

Failed to delete connection
Cannot delete connection 2 because it is used by 1 project(s): 'checkout-api' (ID: 1).
Change the git source of those projects (or delete them) first.
...after unlinking: "Connection deleted successfully"  (conns -> 0)

Two problems this run caught (fixed in 1981629)

  • "Delete connection" only rendered for GitHub App providers. A GitLab PAT connection had no delete affordance at all — the second half of the reported dead end. DELETE /git-connections/{id} is generic, so it now shows for every provider type; the server-side project guard is unchanged.
  • Naming the blocking projects loaded whole project models, so the check died with Database Error: unexpected value for Preset enum: node on a project row whose enum value this build couldn't decode — replacing the actionable message with an opaque one. Now selects id/name/slug only. (The pre-existing check_provider_deletion_safety had the same exposure and is fixed too.)

Re-verified after the follow-up: cargo test -p temps-git --lib → 299 passed, clippy clean, tsc --noEmit clean.

@dviejokfs
dviejokfs merged commit 1ee3edc into main Aug 5, 2026
24 checks passed
@dviejokfs
dviejokfs deleted the fix/git-provider-delete-invisible-connections branch August 6, 2026 22:12
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.

1 participant