feat: enforce required baseline issue statuses with custom status support #681

Closed
opened 2026-06-21 16:02:05 +00:00 by jmiller · 1 comment
Owner

Summary

Since the custom status dropdown is replacing the traditional open/closed buttons, there needs to be a guaranteed set of baseline statuses that always exist. Organizations can add custom statuses on top of these, but the required set cannot be deleted.

Required Baseline Statuses

These statuses must always exist for every org and cannot be removed:

Status Maps To Description
Open open New/active issue
In Progress open Actively being worked on
Waiting open Blocked or waiting on external input
In Review open PR submitted, awaiting review
Closed closed Completed/resolved
Won't Fix closed Declined/not planned

Current Behavior

  • Orgs can create custom statuses via /-/admin/issue-statuses or API
  • Statuses can be freely deleted, including ones that issues depend on
  • No enforcement of a minimum required set

Desired Behavior

  • On org creation: seed the 6 required baseline statuses automatically
  • Prevent deletion of required statuses (API returns 403, UI hides delete button)
  • Allow renaming of required statuses (e.g., "Won't Fix" → "Not Planned")
  • Allow reordering of all statuses
  • Custom statuses can be added/deleted freely on top of the baseline
  • Required statuses flagged with is_required: true in the database

Implementation Notes

  • Add is_required bool column to issue_status_definition table
  • Seed required statuses in migration or org creation hook
  • Block DELETE API for statuses where is_required = true
  • UI: hide delete icon for required statuses, show lock icon instead
  • The status dropdown on issues replaces the close button — these baseline statuses ensure there's always a way to close/reopen issues
## Summary Since the custom status dropdown is replacing the traditional open/closed buttons, there needs to be a guaranteed set of baseline statuses that always exist. Organizations can add custom statuses on top of these, but the required set cannot be deleted. ## Required Baseline Statuses These statuses must always exist for every org and cannot be removed: | Status | Maps To | Description | |--------|---------|-------------| | Open | open | New/active issue | | In Progress | open | Actively being worked on | | Waiting | open | Blocked or waiting on external input | | In Review | open | PR submitted, awaiting review | | Closed | closed | Completed/resolved | | Won't Fix | closed | Declined/not planned | ## Current Behavior - Orgs can create custom statuses via `/-/admin/issue-statuses` or API - Statuses can be freely deleted, including ones that issues depend on - No enforcement of a minimum required set ## Desired Behavior - On org creation: seed the 6 required baseline statuses automatically - Prevent deletion of required statuses (API returns 403, UI hides delete button) - Allow renaming of required statuses (e.g., "Won't Fix" → "Not Planned") - Allow reordering of all statuses - Custom statuses can be added/deleted freely on top of the baseline - Required statuses flagged with `is_required: true` in the database ## Implementation Notes - Add `is_required bool` column to `issue_status_definition` table - Seed required statuses in migration or org creation hook - Block DELETE API for statuses where `is_required = true` - UI: hide delete icon for required statuses, show lock icon instead - The status dropdown on issues replaces the close button — these baseline statuses ensure there's always a way to close/reopen issues
Author
Owner

Critical: Open/Closed must be indestructible

The Open and Closed statuses are the two that map directly to Gitea's internal is_closed boolean on issues. If a user deleted either of these:

  • No way to close issues (if Closed deleted)
  • No way to reopen issues (if Open deleted)
  • API calls that set state: "closed" or state: "open" would have no status to resolve to
  • Filters like is:open and is:closed would break

Safety layers needed:

  1. Database: is_required = true column, seeded on org creation
  2. API: DELETE endpoint returns 403 Forbidden with message "Cannot delete required status"
  3. UI: Hide delete button for required statuses, show a lock icon with tooltip "Required status — cannot be deleted"
  4. Fallback: Even if someone bypasses the API (direct DB edit), the issue state transition code should fall back to the default Open/Closed if the mapped status doesn't exist

The other baseline statuses (In Progress, Waiting, In Review, Won't Fix) are strongly recommended but their deletion wouldn't break core functionality — only Open and Closed are truly critical.

## Critical: Open/Closed must be indestructible The `Open` and `Closed` statuses are the two that map directly to Gitea's internal `is_closed` boolean on issues. If a user deleted either of these: - No way to close issues (if `Closed` deleted) - No way to reopen issues (if `Open` deleted) - API calls that set `state: "closed"` or `state: "open"` would have no status to resolve to - Filters like `is:open` and `is:closed` would break ### Safety layers needed: 1. **Database**: `is_required = true` column, seeded on org creation 2. **API**: DELETE endpoint returns `403 Forbidden` with message "Cannot delete required status" 3. **UI**: Hide delete button for required statuses, show a lock icon with tooltip "Required status — cannot be deleted" 4. **Fallback**: Even if someone bypasses the API (direct DB edit), the issue state transition code should fall back to the default Open/Closed if the mapped status doesn't exist The other baseline statuses (In Progress, Waiting, In Review, Won't Fix) are strongly recommended but their deletion wouldn't break core functionality — only `Open` and `Closed` are truly critical.
Sign in to join this conversation.