Skip to content

GitLab Assignment Locking ​

Two developers working the same issue simultaneously = merge hell. Spexso prevents this with a GitLab assignment lock enforced at the /spexso-start level.

Rules ​

  • On /spexso-start <id>: Check issue assignment via npx spexso issue view <id>
  • Unassigned: Proceed — assign issue to current user via npx spexso issue update <id> --assignee $(npx spexso whoami)
  • Assigned to me: Proceed — resume work (worktree may or may not exist)
  • Assigned to someone else: BLOCK — "Issue #42 is assigned to @dev-b. Pick a different issue or coordinate with them."

Lock Check in /spexso-start ​

bash
# Check assignment (issue view prints the normalized Issue shape)
assignee=$(npx spexso issue view $ISSUE_ID | jq -r '.assignee // empty')
me=$(npx spexso whoami --output json | jq -r '.username')

if [ -n "$assignee" ] && [ "$assignee" != "$me" ]; then
  echo "Issue #$ISSUE_ID is assigned to @$assignee."
  echo "Pick a different issue or ask @$assignee to unassign."
  exit 1
fi

# Claim the issue
npx spexso issue update $ISSUE_ID --assignee $me

# Also check if remote branch already exists (belt + suspenders)
if git ls-remote --heads origin "spexso/issue-$ISSUE_ID" | grep -q .; then
  echo "Branch spexso/issue-$ISSUE_ID exists on remote."
  echo "Someone may be working on this. Check with the team."
  # Warning, not a hard block — branch could be from a previous attempt
fi

On /spexso-finish

The assignment stays through merge — /spexso-finish moves the issue to stage::ship + needs::manual-test, but does not unassign it. Unassignment isn't automated: the developer clears needs::manual-test after manual QA and moves the issue to stage::operate themselves. This keeps a clear ownership trail visible on the GitLab board until a human confirms the work is live.

Auto-pick runner ​

The auto-pick runner acts as its own bot user, so the assignment lock works in both directions: the marathon skips issues assigned to a developer, and /spexso-start blocks on an issue assigned to the bot.

On the runner path (/spexso-start --autopick) the soft "branch exists" warning is replaced by an atomic claim, so a developer and the bot cannot both win the same issue:

bash
git push origin HEAD:refs/heads/spexso/issue-$ISSUE_ID --force-with-lease=refs/heads/spexso/issue-$ISSUE_ID:

The empty expected value means "the branch must not exist yet" and the server does the compare-and-swap. The flag goes after the refspec on purpose: the permission policy denies git push --force* as a prefix match. If the push is rejected, the bot hands the issue off: it unassigns itself, removes auto-pick, adds needs::attention and notes that the branch already exists, so the issue is not picked again on the next run.

Right before the push and auto-merge, /spexso-finish --train-merge --autopick re-reads the assignee. If a developer took the issue over in the meantime, the bot removes auto-pick, posts a note and leaves the MR open without auto-merge.

A human /spexso-start on an auto-pick issue drops the auto-pick label, so the issue leaves the bot's queue.