Compare commits

..
5 Commits
Author SHA1 Message Date
lukaszraczylo 0a2469ef7d fix(gh): bypass the stale run cache with a created filter
GitHub answered the confirming run query with a weeks-old cached result
even with page=1, so alerts for fixed workflows stayed. A created filter
changes the query each day and reads current runs.
2026-10-05 11:16:37 +01:00
github-actions[bot]andGitHub 06922abe05 chore(deps): update dependency @lucide/vue to v1.51.0
Automated dependency update. All tests passed.
2026-10-05 09:37:40 +00:00
lukaszraczylo 2100458973 chore(renovate): hold pnpm below 12
The pinned pnpm 11 in workflow-prepare.sh and ci.yaml cannot switch to the
pnpm 12 native binary. Node runs the binary as JavaScript and the install fails.
2026-10-05 10:34:16 +01:00
github-actions[bot]andGitHub 6b08d09431 chore(deps): update dependencies
Automated dependency update. All tests passed.
2026-10-05 03:22:21 +00:00
lukaszraczylo 78a6723143 fix(web): run automatic actions after a repository rescan
The rescan button of one repository now triggers automatic actions too. The webhook calls run in the background so the response does not wait for them.
2026-10-05 01:57:05 +01:00
7 changed files with 122 additions and 65 deletions
+1 -1
View File
@@ -96,7 +96,7 @@ actions:
### Automatic actions
An action with `auto: true` fires without a click. After every scan, and once at startup, the server sends the webhook for each current alert whose kind is in `kinds`. It sends once per action, repo, alert URL and head commit. A new commit on a pull request is a new send.
An action with `auto: true` fires without a click. After every scan, after every single-repository rescan, and once at startup, the server sends the webhook for each current alert whose kind is in `kinds`. It sends once per action, repo, alert URL and head commit. A new commit on a pull request is a new send.
```yaml
actions:
+11 -3
View File
@@ -119,10 +119,13 @@ func (c *Collector) OnRefresh(fn func(context.Context, View)) { c.onRefresh = ap
func (c *Collector) refreshAndNotify(ctx context.Context) {
c.Refresh(ctx)
if len(c.onRefresh) == 0 || ctx.Err() != nil {
if ctx.Err() != nil {
return
}
v := c.View()
c.notify(ctx, c.View())
}
func (c *Collector) notify(ctx context.Context, v View) {
for _, fn := range c.onRefresh {
fn(ctx, v)
}
@@ -239,7 +242,12 @@ func (c *Collector) RefreshRepo(ctx context.Context, name string) (View, error)
c.mu.Lock()
c.repos[canon] = r
c.mu.Unlock()
return c.View(), nil
v := c.View()
if len(c.onRefresh) > 0 {
// The request context ends with the response, which must not wait for webhooks.
go c.notify(context.WithoutCancel(ctx), v)
}
return v, nil
}
func (c *Collector) configured(name string) (string, bool) {
+34
View File
@@ -519,3 +519,37 @@ func TestPROpenDoesNotChangeCounts(t *testing.T) {
t.Fatalf("view = %+v", v)
}
}
func TestRefreshRepoRunsRefreshHooksWithTheNewView(t *testing.T) {
f := &fakeFetcher{fn: func(name string) (model.Repo, error) {
return model.Repo{FullName: name, URL: "u", Issues: make([]model.Issue, 3)}, nil
}}
c := newCollector(f, "o/a")
got := make(chan View, 1)
c.OnRefresh(func(_ context.Context, v View) { got <- v })
if _, err := c.RefreshRepo(context.Background(), "o/a"); err != nil {
t.Fatal(err)
}
select {
case v := <-got:
if len(v.Repos) != 1 || len(v.Repos[0].Issues) != 3 {
t.Fatalf("hook saw a stale view: %+v", v.Repos)
}
case <-time.After(2 * time.Second):
t.Fatal("refresh hook did not run after a repository refresh")
}
}
func TestRefreshRepoFailureSkipsRefreshHooks(t *testing.T) {
c := newCollector(&fakeFetcher{fn: func(string) (model.Repo, error) { return model.Repo{}, errors.New("nope") }}, "o/a")
called := make(chan struct{}, 1)
c.OnRefresh(func(context.Context, View) { called <- struct{}{} })
if _, err := c.RefreshRepo(context.Background(), "unknown/x"); err == nil {
t.Fatal("unknown repository must fail")
}
select {
case <-called:
t.Fatal("hook ran for a refused refresh")
case <-time.After(200 * time.Millisecond):
}
}
+14 -10
View File
@@ -14,14 +14,16 @@ import (
)
const (
maxFailingNames = 5
runsPerWorkflow = 5
maxWorkflows = 100
eventPullRequest = "pull_request"
eventPullReqTgt = "pull_request_target"
statusStateFail = "failure"
statusStateError = "error"
statusStatePend = "pending"
maxFailingNames = 5
runsPerWorkflow = 5
// freshRunsWindowDays bounds the created filter of the confirming run query.
freshRunsWindowDays = 30
maxWorkflows = 100
eventPullRequest = "pull_request"
eventPullReqTgt = "pull_request_target"
statusStateFail = "failure"
statusStateError = "error"
statusStatePend = "pending"
)
// backgroundEvents are triggers that run without a person pushing code.
@@ -374,9 +376,11 @@ func (c *Client) workflowRuns(ctx context.Context, full string, id int64, branch
path := fmt.Sprintf("/repos/%s/actions/workflows/%d/runs", full, id)
get := c.getJSON
if fresh {
// GitHub can keep answering one exact query from an old cached result, for weeks, whatever
// the connection. A different query string skips that entry and reads current runs.
// GitHub can keep answering a branch query from an old cached result, for weeks, whatever
// the connection, and page=1 alone does not skip it. A created filter changes the query
// every day and reads current runs.
q.Set("page", "1")
q.Set("created", ">="+c.now().UTC().AddDate(0, 0, -freshRunsWindowDays).Format(time.DateOnly))
get = c.getJSONFresh
}
if err := get(ctx, full, path, q, &out); err != nil {
+2 -2
View File
@@ -461,12 +461,12 @@ func TestAPIErrorMessageParsing(t *testing.T) {
}
// staleThenFresh models GitHub answering one exact query from an old cached result: the plain
// query returns an old failing run every time, and the same query with page=1 returns current runs.
// query returns an old failing run every time, and the same query with a created filter returns current runs.
func staleThenFresh(f *fakeGitHub, path string) *atomic.Int64 {
var calls atomic.Int64
f.routes[path] = func(w http.ResponseWriter, r *http.Request) {
calls.Add(1)
if r.URL.Query().Get("page") == "" {
if r.URL.Query().Get("created") == "" {
_, _ = w.Write([]byte(`{"workflow_runs":[
{"id":1,"html_url":"old","event":"schedule","head_branch":"main","status":"completed","conclusion":"failure","created_at":"2026-09-11T03:02:00Z"}]}`))
return
+49 -49
View File
@@ -25,7 +25,7 @@ importers:
version: 3.3.3(@fortawesome/[email protected])([email protected]([email protected]))
'@lucide/vue':
specifier: ^1.49.0
version: 1.49.0([email protected]([email protected]))
version: 1.51.0([email protected]([email protected]))
'@vueuse/core':
specifier: ^15.0.0
version: 15.0.0([email protected]([email protected]))
@@ -50,7 +50,7 @@ importers:
devDependencies:
'@eslint/js':
specifier: ^10.0.1
version: 10.0.1([email protected]1.0([email protected]))
version: 10.0.1([email protected]2.0([email protected]))
'@tailwindcss/vite':
specifier: ^4.3.3
version: 4.3.3([email protected](@types/[email protected])([email protected]))
@@ -65,10 +65,10 @@ importers:
version: 2.5.1(@vue/[email protected])(@vue/[email protected])([email protected]([email protected]))
eslint:
specifier: ^10.11.0
version: 10.11.0([email protected])
version: 10.12.0([email protected])
eslint-plugin-vue:
specifier: ^10.11.1
version: 10.11.1(@typescript-eslint/[email protected]([email protected]1.0([email protected]))([email protected]))([email protected]1.0([email protected]))([email protected]([email protected]1.0([email protected])))
version: 10.11.1(@typescript-eslint/[email protected]([email protected]2.0([email protected]))([email protected]))([email protected]2.0([email protected]))([email protected]([email protected]2.0([email protected])))
globals:
specifier: ^17.12.0
version: 17.13.0
@@ -86,7 +86,7 @@ importers:
version: 5.9.3
typescript-eslint:
specifier: ^8.70.1
version: 8.71.0([email protected]1.0([email protected]))([email protected])
version: 8.71.0([email protected]2.0([email protected]))([email protected])
vite:
specifier: ^8.3.1
version: 8.3.2(@types/[email protected])([email protected])
@@ -95,7 +95,7 @@ importers:
version: 5.0.3(@types/[email protected])([email protected])([email protected](@types/[email protected])([email protected]))
vue-tsc:
specifier: ^3.3.11
version: 3.3.11([email protected])
version: 3.3.12([email protected])
packages:
@@ -248,8 +248,8 @@ packages:
'@keyv/[email protected]':
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
'@lucide/vue@1.49.0':
resolution: {integrity: sha512-7xS5gYnLoIx0jlmlRE3Rb71fIHaNqhVBiKMpOlqrYt6nMPy3C/62HJHo5XNsV3HTK+9l3M0hXICrIA7S5v6y5Q==}
'@lucide/vue@1.51.0':
resolution: {integrity: sha512-zMkTAMP8OsPESz/GDedsZfr3VUF2IiHlj25mBZG6Pc8A8J8BdygkE8k1HhRFXEUNzytp5DqxiBQNm3RG/U2bVg==}
peerDependencies:
vue: '>=3.0.1'
@@ -605,8 +605,8 @@ packages:
'@vue/[email protected]':
resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==}
'@vue/[email protected]1':
resolution: {integrity: sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==}
'@vue/[email protected]2':
resolution: {integrity: sha512-k7uIKR0n4CBidk233y7cac9osnDQ5WRidTl6nsrNvpZ3nf8cx9iJxRVsRzVsoPRHbowT/iKgGMTS9RGov3jeIA==}
'@vue/[email protected]':
resolution: {integrity: sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA==}
@@ -806,8 +806,8 @@ packages:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
[email protected]1.0:
resolution: {integrity: sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==}
[email protected]2.0:
resolution: {integrity: sha512-npHjrHb2o4ehH3mE+YuxABR+0gyb0aVWCIolgJDArwCiHEIsonBm3ZKjel5b0U5+a+MbEvIyRCGCzD3eKKK2NA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
@@ -1435,8 +1435,8 @@ packages:
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
[email protected]1:
resolution: {integrity: sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==}
[email protected]2:
resolution: {integrity: sha512-kbpaz18O9jPVkXVHD1BEjpUXM6WhXSu+62L1sGGr6Wbzd8GbvkBQWjoj3KZ/C0aq2MZllce7UdtWzwCIYsBMGA==}
hasBin: true
peerDependencies:
typescript: '>=5.0.0'
@@ -1514,9 +1514,9 @@ snapshots:
hashery: 1.5.1
keyv: 5.6.0
'@eslint-community/[email protected]([email protected]1.0([email protected]))':
'@eslint-community/[email protected]([email protected]2.0([email protected]))':
dependencies:
eslint: 10.11.0([email protected])
eslint: 10.12.0([email protected])
eslint-visitor-keys: 3.4.3
'@eslint-community/[email protected]': {}
@@ -1537,9 +1537,9 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
'@eslint/[email protected]([email protected]1.0([email protected]))':
'@eslint/[email protected]([email protected]2.0([email protected]))':
optionalDependencies:
eslint: 10.11.0([email protected])
eslint: 10.12.0([email protected])
'@eslint/[email protected]': {}
@@ -1638,7 +1638,7 @@ snapshots:
'@keyv/[email protected]': {}
'@lucide/vue@1.49.0([email protected]([email protected]))':
'@lucide/vue@1.51.0([email protected]([email protected]))':
dependencies:
vue: 3.5.43([email protected])
@@ -1797,15 +1797,15 @@ snapshots:
dependencies:
'@types/node': 26.6.4
'@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected]1.0([email protected]))([email protected]))([email protected]1.0([email protected]))([email protected])':
'@typescript-eslint/[email protected](@typescript-eslint/[email protected]([email protected]2.0([email protected]))([email protected]))([email protected]2.0([email protected]))([email protected])':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.71.0([email protected]1.0([email protected]))([email protected])
'@typescript-eslint/parser': 8.71.0([email protected]2.0([email protected]))([email protected])
'@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/type-utils': 8.71.0([email protected]1.0([email protected]))([email protected])
'@typescript-eslint/utils': 8.71.0([email protected]1.0([email protected]))([email protected])
'@typescript-eslint/type-utils': 8.71.0([email protected]2.0([email protected]))([email protected])
'@typescript-eslint/utils': 8.71.0([email protected]2.0([email protected]))([email protected])
'@typescript-eslint/visitor-keys': 8.71.0
eslint: 10.11.0([email protected])
eslint: 10.12.0([email protected])
ignore: 7.0.11
natural-compare: 1.4.0
ts-api-utils: 2.5.0([email protected])
@@ -1813,14 +1813,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected]([email protected]1.0([email protected]))([email protected])':
'@typescript-eslint/[email protected]([email protected]2.0([email protected]))([email protected])':
dependencies:
'@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
'@typescript-eslint/visitor-keys': 8.71.0
debug: 4.4.3
eslint: 10.11.0([email protected])
eslint: 10.12.0([email protected])
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -1843,13 +1843,13 @@ snapshots:
dependencies:
typescript: 5.9.3
'@typescript-eslint/[email protected]([email protected]1.0([email protected]))([email protected])':
'@typescript-eslint/[email protected]([email protected]2.0([email protected]))([email protected])':
dependencies:
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
'@typescript-eslint/utils': 8.71.0([email protected]1.0([email protected]))([email protected])
'@typescript-eslint/utils': 8.71.0([email protected]2.0([email protected]))([email protected])
debug: 4.4.3
eslint: 10.11.0([email protected])
eslint: 10.12.0([email protected])
ts-api-utils: 2.5.0([email protected])
typescript: 5.9.3
transitivePeerDependencies:
@@ -1872,13 +1872,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/[email protected]([email protected]1.0([email protected]))([email protected])':
'@typescript-eslint/[email protected]([email protected]2.0([email protected]))([email protected])':
dependencies:
'@eslint-community/eslint-utils': 4.10.1([email protected]1.0([email protected]))
'@eslint-community/eslint-utils': 4.10.1([email protected]2.0([email protected]))
'@typescript-eslint/scope-manager': 8.71.0
'@typescript-eslint/types': 8.71.0
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
eslint: 10.11.0([email protected])
eslint: 10.12.0([email protected])
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -1962,7 +1962,7 @@ snapshots:
'@vue/[email protected]': {}
'@vue/[email protected]1':
'@vue/[email protected]2':
dependencies:
'@volar/language-core': 2.4.28
'@vue/compiler-dom': 3.5.43
@@ -2129,18 +2129,18 @@ snapshots:
[email protected]: {}
[email protected](@typescript-eslint/[email protected]([email protected]1.0([email protected]))([email protected]))([email protected]1.0([email protected]))([email protected]([email protected]1.0([email protected]))):
[email protected](@typescript-eslint/[email protected]([email protected]2.0([email protected]))([email protected]))([email protected]2.0([email protected]))([email protected]([email protected]2.0([email protected]))):
dependencies:
'@eslint-community/eslint-utils': 4.10.1([email protected]1.0([email protected]))
eslint: 10.11.0([email protected])
'@eslint-community/eslint-utils': 4.10.1([email protected]2.0([email protected]))
eslint: 10.12.0([email protected])
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 7.1.6
semver: 7.8.5
vue-eslint-parser: 10.4.1([email protected]1.0([email protected]))
vue-eslint-parser: 10.4.1([email protected]2.0([email protected]))
xml-name-validator: 5.0.0
optionalDependencies:
'@typescript-eslint/parser': 8.71.0([email protected]1.0([email protected]))([email protected])
'@typescript-eslint/parser': 8.71.0([email protected]2.0([email protected]))([email protected])
[email protected]:
dependencies:
@@ -2153,9 +2153,9 @@ snapshots:
[email protected]: {}
[email protected]1.0([email protected]):
[email protected]2.0([email protected]):
dependencies:
'@eslint-community/eslint-utils': 4.10.1([email protected]1.0([email protected]))
'@eslint-community/eslint-utils': 4.10.1([email protected]2.0([email protected]))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.7.0
@@ -2603,13 +2603,13 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
[email protected]([email protected]1.0([email protected]))([email protected]):
[email protected]([email protected]2.0([email protected]))([email protected]):
dependencies:
'@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/[email protected]([email protected]1.0([email protected]))([email protected]))([email protected]1.0([email protected]))([email protected])
'@typescript-eslint/parser': 8.71.0([email protected]1.0([email protected]))([email protected])
'@typescript-eslint/eslint-plugin': 8.71.0(@typescript-eslint/[email protected]([email protected]2.0([email protected]))([email protected]))([email protected]2.0([email protected]))([email protected])
'@typescript-eslint/parser': 8.71.0([email protected]2.0([email protected]))([email protected])
'@typescript-eslint/typescript-estree': 8.71.0([email protected])
'@typescript-eslint/utils': 8.71.0([email protected]1.0([email protected]))([email protected])
eslint: 10.11.0([email protected])
'@typescript-eslint/utils': 8.71.0([email protected]2.0([email protected]))([email protected])
eslint: 10.12.0([email protected])
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -2666,10 +2666,10 @@ snapshots:
dependencies:
vue: 3.5.43([email protected])
[email protected]([email protected]1.0([email protected])):
[email protected]([email protected]2.0([email protected])):
dependencies:
debug: 4.4.3
eslint: 10.11.0([email protected])
eslint: 10.12.0([email protected])
eslint-scope: 9.1.2
eslint-visitor-keys: 5.0.1
espree: 11.2.0
@@ -2678,10 +2678,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
[email protected]1([email protected]):
[email protected]2([email protected]):
dependencies:
'@volar/typescript': 2.4.28([email protected])
'@vue/language-core': 3.3.11
'@vue/language-core': 3.3.12
typescript: 5.9.3
[email protected]([email protected]):
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"packageRules": [
{
"description": "pnpm 11 cannot switch to the pnpm 12 native binary: npx runs it with node",
"matchDepTypes": ["packageManager"],
"matchPackageNames": ["pnpm"],
"allowedVersions": "<12"
}
]
}