## TL;DR
Make the review job survive a missing user instead of dying on it, because large groups always contain a few stale member records. Query the member list, and when one member lookup returns not found, log that member identifier as an exception, check whether it is a renamed user, a deleted account or a project bot, and continue with the rest. Reconcile the exception list against the group member page so the review covers everyone who exists and documents everyone who does not.

```text
GitLab group member access review failed: GraphQL error user not found
```

## Use this when
- A GitLab member export or review script stops with a user not found GraphQL error.
- The failure appeared after account cleanup or a username change.
- Manual review in the UI works but the automated job fails.

## Not for this skill when
- Every member lookup fails. That points at job permissions or the wrong group path, not stale members.
- The group path itself returns not found. Verify the group still exists under that path.

## Steps
1. Capture the member identifier or username in the failing query from the job log and note the group and the page of results being processed. Verify: the failing member reference is recorded exactly.
2. Look the member up in the GitLab admin or group members view and classify it as renamed, deleted, blocked or a bot or service account created by a project. Verify: the member is classified with evidence from the UI.
3. Query the same user through the REST users endpoint or the admin API to see whether GraphQL and REST disagree, which happens with some renamed or internal accounts. Verify: the cross check result is recorded for the member.
4. Update the job to catch the not found case per member, write the member to an exceptions list with its classification, and continue processing the remaining members. Verify: a rerun completes the member list with exceptions logged instead of an abort.
5. For renamed users, update the review mapping to the current username so their access is still reviewed under the right identity this cycle. Verify: renamed users appear in the review under their current names.
6. Reconcile the final review population plus exceptions against the group member count shown in GitLab, and attach the exception list to the review evidence. Verify: reviewed plus excepted equals the group total.
7. Raise the stale member records for cleanup with the group owners so the next cycle starts from a member list that resolves cleanly. Verify: a cleanup task exists naming the stale records.
8. Schedule a monthly stale member scan that resolves every group member identifier against live user records and reports the ones that no longer resolve, giving owners a cleanup list long before the quarterly review depends on it. Verify: the scan runs on schedule and its latest exception list is attached to the group owner tasks.

## Why it happens
GraphQL member queries resolve each member to a user object, and a single member whose account was deleted, renamed in a way the cache has not caught, or created as an internal bot can return null where the job expects a user. Scripts written against tidy test groups assume every member resolves, so production groups break them. Treating not found as a per member exception with classification keeps the review complete and honest: real access gets reviewed, ghosts get documented, and owners get a cleanup list.

## Variants and edge cases

### GitLab GraphQL group members query returns null user
The null phrasing. Guard the user field before reading its attributes; the null is data, not a query syntax fault.

### member review fails after user deleted from GitLab
The deletion phrasing. The membership record can outlive the account briefly; classify and document it.

### bot user breaks GitLab access review export
The bot phrasing. Project and group bots need an owner based review path, since no human will certify them.

Edge cases to check before closing the review cycle:
- Renamed users keep their numeric identifier. Match on the identifier in tooling, not the username, to survive renames.
- A blocked user still holds membership until removed. Review their access as a finding, not an exception.
- Subgroup members inherited into a parent review can confuse counts. Reconcile at the level the review was scoped.

Compatibility: GitLab groups and members, GitLab GraphQL and REST APIs, self-managed and GitLab.com instances.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_1l-5ESBCazbnkJDDtf_s4A
