Cache saves have been rejected since June because the repo sat above its
10 GB allowance, so every PR run fell back to a cache built with an
older toolchain and rebuilt the backend from scratch.
- go version in the `gobuild` and `golint` keys and `restore-keys`
- seeder triggers on `go.mod` and gained `workflow_dispatch`
- single writer for the `gomod` cache, the two were racing
- new daily `cache-prune` workflow holding the total under a limit
- `cache: false` for `setup-go` in release and cron workflows, it held
815 MB
- add `workflow_dispatch` so the cache workflows can be triggered
on-demand
Assisted-by: Claude Code:claude-opus-5
The api specs were Go templates whose committed form was not a valid
swagger document, so `swagger-validate`, `generate-openapi.go` and
`.spectral.yaml` each worked around it. They are now plain json,
substituted at serve time.
Renaming them off `.tmpl` also stops `make fmt` rewriting them, which
used to bump their mtime and silently skip the next `make
generate-swagger`.
Also enables stricter spectral linting: extends `lint-swagger` to the
OpenAPI 3 spec, turns on `openapi-tags`, `operation-singular-tag` and
`operation-tag-defined`, adds a top-level `tags` array with descriptions
to the swagger input, and drops the redundant `repository` tag from
`POST /user/repos`.
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
Fixes a `runtime error: index out of range [0] with length 0` panic when
executing template `repo/diff/section_unified` because
`GetLineTypeMarker()` indexes into empty `DiffLine` content.
The regression was introduced in PR #38706 (`94c61137e4`) where
`Content: " "` was removed from the initialization of `tailDiffLine`.
Guarding `GetLineTypeMarker()` directly makes Gitea defensive and robust
against empty diff line values regardless of the source path.
Fixes https://github.com/go-gitea/gitea/issues/38724
### Tests
- Added `TestDiffLine_GetLineTypeMarker` covering empty content,
prefixes, and normal text formats.
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
Further refinements to AGENTS.md:
- consolidate some points
- add point about locales
- rewrite some points for clarity and size
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: delvh <dev.lh@web.de>
`.markup del code { text-decoration: inherit }` makes inline code inside
`<del>` paint its own line-through in addition to the one already
propagating from the parent. Since `code` is `font-size: 85%`, the two
land at different heights and render as a doubled strikethrough.
The rule was inherited from primer-markdown, where it was added in
https://github.com/primer/css/commit/762b8b8264aa4c4beed0a7f842f90c142eb2b310
("so that `<del>` or `<a>` has the same effects on `<code>` tags") at a
time when inline `code` was `display: inline-block`, a box that text
decorations do not propagate into. That `display: inline-block` was
removed 11 days later in
https://github.com/primer/css/commit/f1131d5ab618ac41d4097823b511ca0b373b2ee2,
which made the rule redundant, but it survived the squashed import into
primer/css and every copy downstream of it.
No other popular markdown stylesheet carries an equivalent rule, GitHub
still ships it and shows the same doubled line.
Fixes: https://github.com/go-gitea/gitea/issues/34786
Repository create and delete already fire `repository` webhooks, rename
did not. This adds the `renamed` action with `changes.name.from`
carrying the previous name, and renders it in the chat converters.
Actions workflows are unaffected, they still do not trigger on rename.
AI assistance was used for the implementation and tests.
Fixes https://github.com/go-gitea/gitea/issues/34891.
Co-authored-by: roman s <roman.sukach@dust-labs.com>
Co-authored-by: silverwind <me@silverwind.io>
UploadHandler creates the LFS meta object only as the last step of
uploadOrVerify, after the content is already in the store, so a request
that errors has never created a row of its own. The removal on the error
path was a real compensating action when it was added in #14726, where
the meta object was created before contentStore.Put, but #16865 moved
creation after the Put and left the removal behind.
Since then it can only ever delete a row created by a different request:
a stalled git-lfs PUT that fails after its own retry has already
succeeded wipes the winner's meta object, leaving the content in the
store unreachable and eligible for orphan cleanup.
Drop the removal and add a regression test asserting that a failing
upload keeps a pre-existing meta object.
Fixes: #38424
Assisted-by: Claude Code:claude-opus-5
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Adds a `tag_filter` query parameter to `GET /repos/{owner}/{repo}/releases` that filters releases by tag name, with `*` as a wildcard (e.g. `v1*`, `*beta`, `*rc*`). Matching is
case-insensitive and done in the database query. Literal `%`, `_` and `\` in the filter are escaped.
Fixes: https://github.com/go-gitea/gitea/issues/38513
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This pull request optimizes code diff highlighting in the PR Files
Changed view, which is a major CPU bottleneck and hot path in
production.
Previously, Gitea executed the expensive `DiffMatchPatch` algorithm
twice for every matching deleted/added line pair (once for the deleted
line, and once for the added line). We now run the diff algorithm once
and cache the resulting `DiffInline` on the `DiffLine` struct itself,
allowing the second line to render via a 0-cost cache lookup.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Follow-up to https://github.com/go-gitea/gitea/pull/36564 (dynamic
matrix) and https://github.com/go-gitea/gitea/pull/36357 (max-parallel),
fixing issues found reviewing the two features together.
- **A placeholder could stall its run forever.** Its payload keeps the
raw matrix but loses its `needs`, so `ParseJob` re-expanded it instead
of reading it back — fatal for `include: ${{ fromJson(needs.*.outputs.*)
}}`.
- **An `if:` reading `matrix.*` skipped the whole job**, with or without
the `${{ }}`. It now reduces to the needs gate, except under
`always()`/`failure()`/`cancelled()`, and each combination is decided on
its own values once the matrix expands.
- **Dependents could be skipped before the combinations ran**, since
inserted siblings are absent from the resolver's job set. The pass now
stops after an insert and defers to the re-emit it schedules.
- **Expansion failures stranded the placeholder.** A retryable one is
returned so the queue retries it; a malformed payload fails the job
instead of requeueing forever.
- **Rerun could rewind a pass-through row** into a raw placeholder
keeping its old terminal status, which nothing expands. Now gated on the
anchor itself.
Plus: `max-parallel` distinguishes an unevaluated `${{ }}` (debug) from
a non-numeric literal (warn — it silently drops the cap).
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Show only author and co-authors without committer, and deduplicate the
same user with multiple email addresses.
The commit list "Author" column should not show the committer. This is
somewhat misleading, and arguably showing it on the individual commit
page is sufficient and consistent with other forges.
The same user with multiple email addresses often happens when
DEFAULT_KEEP_EMAIL_PRIVATE is enabled and Gitea does not use an actual
email address by default for edits. Showing the same avatar and name
twice is not helpful then.
Ref #37594Fix#38488
---
Before
<img width="4088" height="2138" alt="before"
src="https://github.com/user-attachments/assets/a9e919f4-72d0-495f-a0b5-ec094f148f5a"
/>
After
<img width="4124" height="2142" alt="after"
src="https://github.com/user-attachments/assets/7212e527-ab83-440a-a8a4-95b5a79b3580"
/>
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Unfortunately, we can't completely remove the ctx from git.Repository,
because the CatFileBatch still heavily depends on a parent context.
If we remove the Repository ctx, the CatFileBatch will become a mess and
create a lot of unnecessary git processes.
http://localhost:3000/-/admin/monitor/perftrace
* Before: open a repo home, dozens of git processes (duplicate cat-file)
* After: only a few (no duplicate cat-file)
Adds dynamic matrix evaluation to Gitea Actions: a job's
`strategy.matrix` can be built from the outputs of the jobs it needs.
```yaml
jobs:
generate:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set.outputs.result }}
steps:
- id: set
run: echo "result=[1,2,3]" >> $GITHUB_OUTPUT
build:
needs: [generate]
runs-on: ubuntu-latest
strategy:
matrix:
version: ${{ fromJson(needs.generate.outputs.matrix) }}
steps:
- run: echo "building ${{ matrix.version }}"
```
Such a matrix cannot be expanded at planning time, so the job is planned
as a single placeholder and expanded by the job emitter once its needs
finish. Each combination is then gated by `if:` and concurrency as
usual.
- A matrix that resolves to no combination fails the job, as on GitHub.
- Expansion is capped at `MaxJobNumPerRun`.
- Workflows without a needs-dependent matrix are unaffected.
Fixes https://github.com/go-gitea/gitea/issues/25179
---------
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: ZPascal <pascal.zimmermann@theiotstudio.com>
Co-authored-by: Claude <claude-sonnet-4-5@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.8) <noreply@anthropic.com>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
### Description
Fixes#38676
The Alpine package registry registers for only, so a request returns .
Clients that probe the index with before fetching it (like and other
-based tools) fail outright.
**Fix:** Change to for the APKINDEX.tar.gz route, matching the pattern
already used by the i386 registry a few lines below.
### Related issue
Closes#38676
Co-authored-by: waterWang <waterWang@users.noreply.github.com>
Regression of the ctx removal from "git.Repository" struct (the old code
was already wrong and can still cause 500, the "ctx removal" just makes
the problem easier to reproduce).
Merge duplicate code.
Reviewd by codex: no actionable findings.
`GetBranch` silently returned soft-deleted branches, contradicting
`IsBranchExist` and forcing callers to manually check `branch.IsDeleted`
everywhere.
Refactored it to `GetBranchExisting` to have a clear behavior: it only
returns the existing branch.
---------
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Add support for `strategy.max-parallel` on Gitea Actions matrix jobs.
**How it works**
Jobs over the limit are inserted as `Blocked` instead of `Waiting`, so
runners never see them. When a job finishes, the job-status resolver
promotes one `Blocked` job per freed slot, in job order. Slots are
counted per `JobID` and scoped by reusable-workflow caller. A
`Cancelling` job still owns its runner, so it keeps its slot.
The cap is applied wherever a job can become `Waiting`: initial insert,
rerun, approval, and resolver promotion.
Best effort, not a hard invariant: two concurrent emitter passes can
each promote into the last slot, overshooting by one. It does not
compound, since every later pass recounts.
**Parsing**
Any YAML number, cast to an int as GitHub does (`1.5` → 1). `0` or
negative means unlimited. Expressions (`${{ ... }}`) are not evaluated
yet and fall back to unlimited.
**Migration**
Adds the `max_parallel` column on `action_run_job`. No index or
constraint changes.
**Compatibility**
Existing rows default to `0`, so behaviour is unchanged. No runner
changes needed: the runner protocol is untouched, and since the server
splits the matrix each runner still receives a single job.
Closes https://github.com/go-gitea/gitea/issues/35561
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: ZPascal <pascal.zimmermann@theiotstudio.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Fixes#38209
OAuth2-linked accounts that sign in via the password form were still
redirected to the provider end_session_endpoint on logout because the
redirect was keyed off account LoginType.
Store the session sign-in method (password vs oauth2) and only use
RP-initiated OIDC logout when this session was authenticated via OAuth2.
Sessions without the new key keep the previous LoginType behavior.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The `configure-aws-credentials` step earlier in the same job exports
**both** `AWS_DEFAULT_REGION` and `AWS_REGION` into `$GITHUB_ENV`
(verified in `exportRegion()` at the pinned SHA `517a711`), so
`secrets.AWS_REGION` (the real AWS region) stays set for every later
step in that job.
The AWS CLI v2 region resolution order is `--region` > `AWS_REGION` >
`AWS_DEFAULT_REGION`. The R2 step only set `AWS_DEFAULT_REGION: auto`,
so the leaked `AWS_REGION` won and R2 rejected it, since R2 only accepts
`wnam`, `enam`, `weur`, `eeur`, `apac`, `oc` or `auto`.
The failure log shows both `AWS_DEFAULT_REGION: auto` and `AWS_REGION:
***` in the step env, which confirms the leak.
### Fix
Set `AWS_REGION: auto` explicitly in the R2 upload step env of all three
release workflows. Step-level `env:` is applied after
`GITHUB_ENV`-derived variables, so this reliably overrides the leaked
value.
No other variable leaks from `configure-aws-credentials` matter here:
`AWS_SESSION_TOKEN` is only exported when a session token exists, and
these workflows use static access keys without `role-to-assume`;
`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are already overridden
in the R2 step.
Consolidating the three duplicated upload steps into a composite action
remains a follow-up, as noted in #38635.
fix#38652
UI part (`.log-msg`) uses "white-space: break-spaces;" so the new line
can be correctly rendered.
---------
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: silverwind <me@silverwind.io>
`RedisBroker.Subscribe` returns before the server acks `SUBSCRIBE`, so a
publish right after it can be dropped, making
`TestRedisBroker/CrossBroker` fail intermittently on loaded CI runners
([example](https://github.com/go-gitea/gitea/actions/runs/30257188479/job/89948425118)).
Each scenario now uses its own topic and waits for `PUBSUB NUMSUB`
before publishing. `MemoryBroker` registers synchronously and skips the
wait.
* Closes#36942
* Fixes#19265
Replaces the SSE-based push channel (`/user/events`) with a WebSocket
endpoint (`/-/ws`).
### What changes
- **New `/-/ws` endpoint** (authenticated). One WebSocket per origin,
shared across tabs via a single `SharedWorker`.
- **Pubsub broker** (`services/pubsub`) for fan-out by topic, behind a
`Broker` interface. `MemoryBroker` is the default (single process); a
Redis backend is available for multi-process setups, configured via
`[websocket].PUBSUB_TYPE` / `PUBSUB_CONN_STR`. The internal Gitea queue
was not usable here because it has FIFO/single-consumer semantics.
- **Push-only event production.** Events are emitted by write-triggered
notifiers — `NotificationCountChange`, `PublishStopwatchesForUser`, and
the logout publisher — wired into the existing `notify.Notifier`
interface. No server-side pollers.
- **Typed pub/sub on the client.** `web_src/js/modules/worker.ts` is a
singleton transport; features subscribe per event type via
`onUserEvent('notification-count', cb)` instead of branching on
`event.data.type`.
- **Wire contract** (`UserEventType` union) is shared between the worker
and consumers via `web_src/js/types.ts`, kept in sync with
`services/websocket/events.go`.
- **Client-side periodic polling fallback** kicks in only when the
WebSocket cannot be established (e.g. proxy blocks WS, browser lacks
module-SharedWorker support).
### What's removed
- `modules/eventsource` (SSE manager, run loop, messenger).
- `/user/events` route and `tests/integration/eventsource_test.go`.
- All server-side polling for stopwatches and notification counts.
### Stopwatch multi-tab fix
The navbar stopwatch icon was previously rendered conditionally on `{{if
$activeStopwatch}}`, so tabs loaded before the timer started had no DOM
element to update. The icon and popup are now always rendered (toggled
with `tw-hidden`), and the start/stop/cancel handlers POST silently so
all open tabs reflect the change in real time.
### Deployment note
WebSocket needs the upgrade headers to pass through a reverse proxy,
e.g. for nginx:
```nginx
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```
Without them the WebSocket cannot be established, and after 3
consecutive failed opens the shared worker signals `push-unavailable`:
the notification count and stopwatch fall back to periodic polling on
the existing `[ui.notification]` timeouts. Real-time push is lost, the
features keep working. The reverse-proxy docs need the same note (see
the `docs-update-needed` label).
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Epid <rexmrj@gmail.com>
Fixes a bug in Actions context generation for `pull_request_target`
workflows where `github.ref` / `gitea.ref` was incorrectly populated
with `refs/heads/owner:branch` instead of `refs/heads/branch`.
### Problem Statement
In `services/actions/context.go`, when constructing the `ref` string for
`pull_request_target` events:
```go
ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name
Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com>
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The handler of `/actions/runs/{run}/approve` doesn't check if the run is
already approved. If a run is re-approved, its jobs' status will be
reset to `StatusWaiting`, causing incorrect job status.
`GET/POST` repository contents endpoints resolve the `ref` query
parameter through `ResolveRefCommit`, which previously only accepted
short branch/tag names or commit IDs. Clients that pass fully-qualified
Git refs (GitHub-compatible), e.g. `ref=refs%2Fheads%2Fmain` or
`ref=refs%2Ftags%2Fv1.0`, received 404 "object does not exist".
This change accepts fully-qualified **`refs/heads/*`** and
**`refs/tags/*`** only, then falls back to the existing short-name and
SHA logic. Other `refs/*` prefixes (e.g. `refs/pull/`, `refs/for/`) are
rejected.
Fixes#38197
---------
Co-authored-by: roman s <roman.sukach@dust-labs.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
The download site’s bandwidth costs are growing rapidly, while
Cloudflare R2 does not charge egress fees. Therefore, we would like to
migrate the download site from S3 to R2.
To ensure a smooth transition, we will keep the existing S3 upload
process temporarily and remove it after the migration is complete.
The upload steps are currently duplicated in three places. They could be
consolidated into a composite action in a separate PR.
The preset we extended only matches a `# renovate:` comment on the line
above the `_VERSION` assignment. Ours trails the value, like the ones in
the `Makefile`, so it never matched and the pin has sat at `43.141.5`
since https://github.com/go-gitea/gitea/pull/37050.
Co-authored-by: Claude (Opus 5) <noreply@anthropic.com>
Fixes jobs that get stuck in `cancelling` after Gitea is restarted while
a job is running.
Reproduction:
1. Run a job with `sleep 100`
2. Stop Gitea and wait 120s (> 100s)
3. Start Gitea — the job is still `running`; cancel it, and it stays in
`cancelling`
## Cause
A cancellation is only ever delivered to a runner as the *response* to
its `UpdateTask`
RPC. A runner that has already given up on the task — it crashed, or it
failed to report
the final state while Gitea was unreachable — never calls `UpdateTask`
again, so it never
learns about the cancellation. The task then sits in `cancelling` until
`stop_zombie_tasks`
reaps it, which needs `ZOMBIE_TASK_TIMEOUT` (10m) of silence and only
runs every 5 minutes.
Cancelling actually made this worse. xorm rewrites the `updated` column
on every `UPDATE`,
even when `Cols()` restricts the update to `status`, so persisting the
`cancelling` status
reset the zombie clock. Pressing cancel pushed the cleanup a full
`ZOMBIE_TASK_TIMEOUT`
into the future instead of bringing it forward.
## Change
`StopTask` now skips the `cancelling` handshake when the task has had no
state report from
its runner for longer than `TaskReportTimeout` (1 minute) and cancels it
directly. This
joins the two existing fallbacks — runner deleted, and runner without
cancelling support —
so every cancel path (web UI, API, concurrency, rerun) is covered.
Runners report the state of a running task every few seconds, so a
minute of silence means
the runner is gone. The value is a constant rather than a setting
because it only decides
whether the runner is still reachable, not whether a task should be
killed —
`ZOMBIE_TASK_TIMEOUT` still owns that.
### Tradeoff
If a runner is alive but has been silent for over a minute and is
cancelled in that window,
it skips the graceful post-step cleanup added in #37275. No work is
lost: the runner still
learns the outcome on its next report, because `UpdateTask` returns the
task status as the
result and the runner stops there. That is the behaviour that existed
before #37275.
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
CommentList.loadLabels keyed the result map by label.ID but looked up by
comment.ID, so every label event fell back to individual DB queries.
This caused log spam for any comment where the label was deleted, since
ToTimelineComment calls LoadLabel unconditionally for all comment types
including those with LabelID=0.
Fix the map key, skip the query when LabelID=0, demote the now rarely
triggered orphaned-label log to Debug, and fix a typo ("Commit" ->
"Comment") in that message.
---------
Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Eliminate two layout shifts in the menu, one related to non-existant
label on page load and one to `0` value rendering.
Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: bircni <bircni@icloud.com>
Replace the previous main-branch fetch with data from npm package
[`linguist-languages`](https://github.com/ikatyang-collab/linguist-languages)
and only run generate targets when actually needed. The removed
`fileFilters` were leftovers from the nolyfill removal.
Several small fixes to the Actions runner management UI.
### Runner task list links to the job, not the workflow run
Relabeled the first column from "Run" to "Job"; it now shows the job ID
and links to the specific job (`/actions/runs/{runID}/jobs/{jobID}`).
Renamed locale key `task_list.run` to `task_list.job`.
### Missing "Disabled" translation
The runner list rendered a grey label via `actions.runners.disabled`,
but that key
did not exist in `locale_en-US.json`, so the raw key string leaked into
the UI.
Replaced `"actions.runners.disabled"` with `"disabled"`.
### Status column sorting ignored active vs idle
Sorting by status ordered purely on `last_online`, but the displayed
status is
computed from both `last_online` (offline) and `last_active` (idle vs
active).
As a result idle runners were interleaved with active ones. Sorting now
ranks by
the computed status (active → idle → offline). Disabled runners sink to
the bottom
of their status group (`is_disabled` as a secondary key), with
`last_online`/`id`
as stable tiebreakers so pagination stays deterministic.
### Status label colors
Active and idle both rendered green. Idle is now yellow, active green,
and
offline/unknown grey; the separate grey "Disabled" badge is unchanged.
This keeps
connectivity visible even for disabled runners (e.g. a disabled runner
still shows
whether it is idle or offline).
<img width="715" height="406" alt="image"
src="https://github.com/user-attachments/assets/9ef06aa8-a870-4de5-9d94-603a58186908"
/>
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
Various fixes to actions
1. **Cap total jobs per run in reusable-workflow expansion** — only
nesting depth was capped, so fan-out + nested reusable workflows could
explode job-row inserts and exhaust the DB from a single push. Now
enforces `MaxJobNumPerRun` in the insert path.
2. **Reject rerun-failed when a run has no failed jobs** — an empty job
list meant "re-run everything", so `rerun-failed` on a green run re-ran
all jobs. Now errors (web + API).
3. **Don't adopt external commit statuses into the legacy hash** — the
pre-#35699 Context-only hash matched API-posted statuses too, collapsing
two same-named workflows into one check. Now limited to Actions-user
rows.
4. **Don't cut post-cancel cleanup short in `StopEndlessTasks`** — the
sweep force-stopped just-cancelled jobs mid-cleanup. Now targets
`StatusRunning` only; stalled cancels stay covered by `StopZombieTasks`.
5. **Avoid redundant run reload in `GenerateGiteaContext`** — resolving
`github.triggering_actor` reloaded the run already passed in. Now loads
only the trigger user via new `ActionRunAttempt.LoadTriggerUser`.
---------
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>