Skip to content

Fix team members missing from assignees when team_unit.access_mode is 0#37365

Draft
pisarz77 wants to merge 1 commit intogo-gitea:mainfrom
pisarz77:fix/team-unit-zero-access-mode
Draft

Fix team members missing from assignees when team_unit.access_mode is 0#37365
pisarz77 wants to merge 1 commit intogo-gitea:mainfrom
pisarz77:fix/team-unit-zero-access-mode

Conversation

@pisarz77
Copy link
Copy Markdown

PR title

Fix team members missing from assignees when team_unit.access_mode is 0

PR summary

Problem

On Gitea instances that have been upgraded across multiple releases (and on databases that have been restored from backups across schema changes), rows in the team_unit table can end up with access_mode = 0 (AccessModeNone) even when the parent team row has a real authorize value — most commonly the organization Owners team with authorize = 4 (AccessModeOwner).

When this happens, any team member who does not already have a row in the access table for a given repo is silently excluded from helpers that filter by team_unit.access_mode. The most user-visible caller is GetRepoAssignees in models/repo/user_repo.go:

Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id").
Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))",
    repo.ID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypePullRequests)

When access_mode = 0 for the Owners team, the >= AccessModeWrite condition fails, so the member is dropped from the result. End users experience:

  • @mention autocomplete in issues and PRs does not suggest the affected user.
  • Assignee / reviewer pickers omit the user even though they can open every repo in the org.
  • Issue-comment email notifications never fire for the affected user, because they are not treated as a valid recipient of the mention.

This has been observed in the wild and lines up with symptoms reported in #34871 ("Cannot add an organization owner as a Pull Request reviewer").

Root cause

The v206 migration (AddAuthorizeColForTeamUnit, added in 1.16) copied team.authorize into team_unit.access_mode as a one-shot fix. It did not defend against subsequent legacy writes (e.g. team editors on very old versions, backup/restore across the schema boundary, external tooling) that left some team_unit rows with access_mode = 0 on databases that have lived through that migration.

There is no invariant in current code that guarantees team_unit.access_mode > 0 whenever team.authorize > 0, and gitea doctor --run check-db-consistency does not check this relationship.

Fix

Add migration 331 (FixTeamUnitZeroAccessMode) that idempotently repairs any remaining broken rows:

UPDATE team_unit
SET access_mode = (SELECT authorize FROM team WHERE team.id = team_unit.team_id)
WHERE access_mode = 0
  AND team_id IN (SELECT id FROM team WHERE authorize > 0)

The shape mirrors the original v206 update. The WHERE clause scopes the fix strictly to rows where the two values have diverged and the team actually grants a real permission, so:

  • Healthy instances are a no-op.
  • Teams intentionally configured with authorize = 0 (no-permission teams) are left alone.
  • The fix is idempotent — running it a second time changes nothing.

After the migration, GetRepoAssignees returns the affected users correctly and the access table is re-derived on next team membership change (or immediately via any write path that calls access_model.RecalculateTeamAccesses). No rows are deleted from access, so permission checks cannot regress.

Testing

models/migrations/v1_26/v331_test.go covers four cases against an in-memory SQLite fixture:

  • Owners-like team (authorize = 4) with broken team_unit rows — repaired to 4.
  • Write-level team (authorize = 2) with broken rows — repaired to 2.
  • None-level team (authorize = 0) with a zero row — not modified (no real permission to copy).
  • Healthy team (authorize = 4, access_mode = 4) — not modified (idempotent).

Reproduction before the fix

Create an organisation, create a local user who signs in via OAuth, add them to the Owners team, and — if the instance is in the affected state — observe that they never appear in @mention autocomplete on repositories in that org even though they are listed under Organisation → Members with the "Owner" role.

Confirming the divergence directly:

SELECT tu.team_id, tu.type, tu.access_mode, t.authorize
FROM team_unit tu
JOIN team t ON t.id = tu.team_id
WHERE tu.access_mode = 0 AND t.authorize > 0;

After upgrading past migration 331, this query returns zero rows.

AI disclosure

Per CONTRIBUTING.md: the migration, test cases, and this description were drafted with AI assistance. I understand the data model involved (verified the actual divergence against the production database that hit this bug), I wrote and reviewed the SQL and test coverage myself, and I am comfortable defending and iterating on the change in review.

On some instances upgraded across multiple releases, `team_unit` rows can
end up with `access_mode = 0` even though the parent `team.authorize` is
non-zero (e.g. the organisation Owners team). This causes team-only
members to be omitted by helpers that filter on
`team_unit.access_mode >= AccessModeWrite`, most notably
`GetRepoAssignees` in `models/repo/user_repo.go`, which breaks:

* @mention autocomplete in issues and pull requests
* assignee / reviewer selection
* email notifications triggered by mentions

The original v206 migration (`AddAuthorizeColForTeamUnit`) copied
`team.authorize` into `team_unit.access_mode` but did not guard against
subsequent writes or restore paths that reintroduced zeros.

Add a follow-up migration that idempotently repairs any remaining
`team_unit.access_mode = 0` rows whose parent team has
`authorize > 0`, mirroring the same update shape as v206 but scoped to
broken rows only. Rows whose parent team legitimately has no permission
(`authorize = 0`) are left untouched.

Signed-off-by: Jakub Pisarczyk <pisarz77@gmail.com>
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Apr 22, 2026
@wxiaoguang
Copy link
Copy Markdown
Contributor

wxiaoguang commented Apr 22, 2026

The proper fix should use GetUserIDsWithUnitAccess or GetTeamUserIDsWithAccessToAnyRepoUnit

@wxiaoguang wxiaoguang marked this pull request as draft April 22, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants