open_issues_count in /user/repos is stale — not invalidated/recomputed on issue create #16
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary: The per-repo aggregate
open_issues_countreturned byGET /user/repos(and the relatedopen_pr_counter) does not reflect live state immediately after issues are created — the denormalized counter is not being invalidated/updated on the data-change path, so consumers that trust it get wrong numbers.Concrete reproduction (observed 2026-05-16 on git.oleks.space):
oleks/fleet(#4, #5, #6) and 2 inoleks/cluster(#29, #30) via the API.GET /user/reposreportedopen_issues_count: 0foroleks/fleet— the repo appeared to have no open issues at all.GET /repos/oleks/fleet/issues?state=openat the same moment correctly returned all 3 newly-created open issues.Impact: Any tooling that inventories issues via the
/user/reposaggregate (dashboards, account sweeps, the steward/issuer automation) silently under-counts and can drop freshly-active repos entirely. The discrepancy is worst exactly after a write, which is when inventory is most likely to be run.Investigation asks:
Repository.NumIssues/NumOpenIssues(or the equivalent counter column feedingopen_issues_count) updated transactionally on issue insert, or only by the periodicCheckRepoStats/repo-stats recompute? If the latter, the API exposes a counter that is correct only between recompute cycles./user/reposcounts are eventually-consistent and steer consumers to per-repo enumeration.Severity: low (data-integrity / observability; no data loss — live endpoints are correct).
Investigated — this is not a bug in the fork, and the title premise ("not invalidated/recomputed on issue create") does not hold for the normal create path.
Findings:
Repository.NumOpenIssuesis a computed field (xorm:"-",models/repo/repo.go), derived inAfterLoad()asnum_issues - num_closed_issueson every repo row read — including/user/repos.services/issue/issue.go→issues_model.NewIssue→NewIssueWithIndex) callsIncrRepoIssueNumbers(repoID, isPull, true)inside the same transaction as the insert, atomically incrementing thenum_issuescolumn. Soopen_issues_countis correct immediately after a create via this path.InsertIssues, used byservices/migrations/gitea_uploader.go) — and that path callsmodels.UpdateRepoStats()at finalize, so it is also consistent once the import completes.Why the repro showed
open_issues_count: 0: the most plausible cause is pre-existing denormalized-counter drift on the specificoleks/fleetrepo row (counters can drift after manual DB operations, restores, or historical bugs), which the periodicCheckRepoStatslater corrects. The live/issues?state=openlist is computed byCOUNT(*)and is unaffected by drift, which is why it was correct while the cached counter was not.Resolution: No source change is warranted — the invalidation-on-write mechanism already exists and is correct. To repair a repo whose denormalized counters have drifted, run
gitea doctor --run check-db-consistency --fix(or wait for the periodicCheckRepoStatsrecompute). Closing as works-as-designed / not-fork-introduced.Investigated by Claude Opus 4.7 (assisting @oleks).