Compare commits

..

53 Commits

Author SHA1 Message Date
alexbelgium
805f225618 fix(komga): bound the readiness probes and log an exhausted wait
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 18:57:32 +02:00
alexbelgium
4f9cbd0e90 fix(komga): poll komga directly instead of bashio::net.wait_for, clarify config path
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 18:41:37 +02:00
alexbelgium
08029b9e37 fix(komga): restore add-on reverted by a transient ghcr login failure
The amd64 builder job failed at docker login (denied: denied) before any build
step ran, which tripped revert-on-failure. Re-running the same commit unchanged
succeeded and both arch images are published.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 18:30:36 +02:00
GitHub Actions
1133720b5b Revert "feat(komga): add Komga comics/manga server add-on with ingress (#2959)"
This reverts commit 4e043f7b94.
2026-08-11 16:06:11 +00:00
GitHub Actions
bc2ef6cbec Revert "GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild]"
This reverts commit 9c36f9b480.
2026-08-11 16:06:11 +00:00
github-actions
9c36f9b480 GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild] 2026-08-11 16:03:42 +00:00
Alexandre
4e043f7b94 feat(komga): add Komga comics/manga server add-on with ingress (#2959)
* feat(komga): add Komga comics/manga server add-on with ingress

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(komga): correct chmod path, PUID default and server-generated absolute urls

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(komga): drop webui, the addon linter rejects it when ingress is enabled

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(komga): review fixes - init order, POSIX healthcheck, drop inert s6 vars

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 18:02:47 +02:00
Rodrigo Scomação do Nascimento
9e4e38535f fix(radarr): bump version so Home Assistant offers the rebuilt image (#2958)
* fix(radarr): bump version so HA offers the rebuilt ls313 image

The `6.3.0.10514` image tag was rebuilt and re-pushed on 2026-08-03 and
now ships LinuxServer.io ls313, while installations made before that date
still run the ls311 build they originally pulled.

Because build.json tracks the floating `lscr.io/linuxserver/radarr:*-latest`
tags, a rebuild silently changes the image contents without changing the
add-on version. The Supervisor decides whether an update exists purely by
comparing the `version` string in config.yaml against the installed one --
it does not compare image digests -- so an unchanged string means the
update is never offered and the new image is never pulled.

Add the local patch counter documented in CLAUDE.md to make the rebuild
visible to the Supervisor. Radarr itself is unchanged at 6.3.0.10514, so
updater.json keeps upstream_version as-is; the updater bot only rewrites
config.yaml when the upstream version moves, matching how lidarr
(3.1.0.4875 -> 3.1.0.4875.1) and bazarr (1.6.0 -> 1.6.0.2) already work.

The dotted `.1` form is required rather than `-1`: AwesomeVersion parses
`6.3.0.10514-1` as an unknown strategy and raises on comparison, whereas
`6.3.0.10514.1` compares as SimpleVer and sorts above `6.3.0.10514`.

* chore(sonarr,prowlarr): update to latest upstream releases

Sonarr   4.0.19.2997 -> 4.0.19.3001 (develop-4.0.19.3001-ls184, 2026-08-11)
Prowlarr 2.6.2.5517.9 -> 2.6.2.5534.9 (nightly-2.6.2.5534-ls9, 2026-08-08)

Both add-ons track a prerelease channel (github_beta), and their build.json
files pin the floating `-develop` / `-nightly` LinuxServer.io tags, so the
rebuild picks up the matching base image on merge.

Unlike radarr, neither add-on was affected by the stale-image problem: the
published images match the versions they claim (sonarr ships ls183 for
4.0.19.2997, prowlarr ships ls9 for 2.6.2.5517), so these are ordinary
version bumps that the weekly updater bot would otherwise pick up.

Sonarr also records the upstream version in ARG BUILD_UPSTREAM, updated
here to match. Prowlarr has no BUILD_UPSTREAM line.
2026-08-11 15:36:31 +02:00
dependabot[bot]
96380fdf5f Bump anthropics/claude-code-action from 1.0.183 to 1.0.187 (#2957)
Bumps [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) from 1.0.183 to 1.0.187.
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](be7b93b190...1623c36729)

---
updated-dependencies:
- dependency-name: anthropics/claude-code-action
  dependency-version: 1.0.187
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-10 20:11:01 +02:00
github-actions
aff8931eaf GitHub bot : README updated 2026-08-10 17:17:43 +00:00
alexbelgium
69a6a1a62f Replace Stars evolution badge with token-based Star History chart 2026-08-10 17:27:02 +02:00
github-actions[bot]
9ffe457e5f Update stargazer map & cache 2026-08-10 14:58:50 +00:00
Alexandre
e219d241bc perf(stargazer-map): negative-cache blank locations for 90 days (#2954)
* perf(stargazer-map): negative-cache blank locations for 90 days

The lookup predicate treated a blank country as "not cached", so all 1689
blank rows of the 2652-row cache were re-queried on every weekly run --
~1689 GitHub API calls plus ~28 minutes of the polite time.sleep(1), to
re-derive the same blank answer. In a 89-user sample of those blanks,
87 (97.8%) simply have no public "location" on their profile, so the
lookups fail permanently rather than transiently.

Add a "last_checked" column to the CSV cache. A blank country is now only
re-queried once its check date is more than 90 days old; a known country is
still never re-queried; a user absent from the cache is queried immediately.
Rows from the old two-column file are treated as checked on 2026-08-10, so
the migration happens in the loader and the next run rewrites the CSV.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(stargazer-map): treat a missing last_checked as never checked

Rows carried over from the two-column CSV are no longer backfilled to the
migration date; an absent, empty or non-ISO-date last_checked now reads as
"never checked" and is looked up on the next run, which stamps it. The first
run after merge therefore does the ~1689-user sweep once, and only after that
does the 90-day cadence take over.

Also addresses the review point that a corrupted last_checked in an already
three-column CSV would compare as "recent" under the lexicographic check and
suppress re-checks indefinitely: load_cache() now validates the cell with
datetime.date.fromisoformat and drops anything that is not a real date.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* style(stargazer-map): add the missing save_cache docstring

Codacy flags C0116 on the touched function.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* style(stargazer-map): capitalize load_cache docstring (pydocstyle D403)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* perf(stargazer-map): cap expired rechecks at 200 per run to stagger them

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(stargazer-map): cap re-checks only, never the first sweep

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:24:38 +02:00
Alexandre
1b0969d537 fix(stargazer-map): resolve countries via ISO code instead of English name (Russia/Turkey/Ivory Coast were dropped) (#2956)
* fix(stargazer-map): resolve countries via ISO code, not English name

username_to_country() matched Nominatim's English display_name against
pycountry, but the two vocabularies disagree: pycountry.countries.lookup()
raises LookupError for "Russia", "Turkey" and "Ivory Coast" (its ISO names
are "Russian Federation", "Türkiye", "Côte d'Ivoire"). Those users were
silently recorded as unknown -- the committed cache has 963 users with a
country and zero Russia, so Russia rendered grey on the map.

Request addressdetails from Nominatim and read address.country_code
instead. No new dependency, same one request per user, and it drops the
reversed-component loop that could false-positive on a city or region
named like a country.

The return value is unchanged: still a pycountry .name string, so the CSV
cache and the ISO-3 rendering lookup are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(stargazer-map): drop non-answer locations before geocoding

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:20:51 +02:00
Alexandre
a830293736 docs(stargazer map): credit OpenStreetMap for the geocoding (#2955)
The country lookup is done with Nominatim, i.e. OpenStreetMap data
(ODbL), which requires attribution wherever the derived data is shown.
The footnote credited only the GitHub profile. Add two lines crediting
Nominatim/OSM for the geocoding specifically -- the country shapes are
plotly's Natural Earth basemap, not OSM.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 16:04:07 +02:00
github-actions[bot]
4f7558050b Update stargazer map & cache 2026-08-10 13:53:06 +00:00
Alexandre
6194f26f00 feat(stargazer-map): readable log-scale map with baked-in stats (#2953)
* feat(stargazer-map): readable log-scale map with baked-in stats

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(stargazer-map): count only current stargazers, honest caption wording

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(stargazer-map): show shares only, drop absolute per-country counts

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 15:16:04 +02:00
Alexandre
39da9cf9b5 chore(skill): prefer reusing existing code for repo homogeneity (#2952)
* chore(skill): prefer reusing existing code for repo homogeneity

The standing rule already demanded the simplest solution; it said nothing
about where that solution should come from. A bespoke-but-simple mechanism
in one add-on is still a second way to solve a problem 120+ add-ons share.

- Standing rule: build out of what exists (.templates/ module, existing
  cont-init script, a sibling add-on's pattern), and match repo naming
  conventions when something new is genuinely needed.
- Step 3 (Plan): search for prior art before ranking mechanism levels; not
  reusing an existing mechanism now requires stating why.
- Step 5 (Simplify): reuse check alongside the existing ones — fold
  near-duplicates in, or justify the divergence in the PR body.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(skill): address Codex and CodeRabbit review feedback

- Prior-art search: the --include='*.sh' --include='config.yaml' allowlist
  missed the repo's main mechanisms. `ARG MODULES=` lives in Dockerfiles and
  s6 v3 services are extensionless `run` files; searching for MODULES= found
  6 files under the allowlist vs 129 (125 Dockerfiles) without it. Widened to
  --exclude-dir=.git and named the two file types explicitly.
- Reuse vs isolation: "fold a near-duplicate into the existing mechanism"
  contradicted traps.md:125, which requires a new numbered script rather than
  editing scripts shared by symlink with the webtop add-ons. Added the carve-out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 15:10:36 +02:00
Alexandre
a04d818479 fix(ci): stop tier 1 wasting its turn budget; escalate max-turns after one retry (#2951)
* fix(ci): stop tier 1 wasting its turn budget; escalate max-turns after one retry

Now that classification actually runs, the 12-turn budget got its first real
exercise — and #2949 died on it. The budget was never the problem; how it was
spent was. Turn-by-turn from that run: 3 turns retrying Bash (not in
allowedTools, and failing against the bubblewrap sandbox that
allowed_non_write_users switches on), 6 hunting .templates/ha_entrypoint.sh and
ha_automodules.sh which are not in the sparse checkout, leaving 3 for the issue.

Fixed at the cause rather than by raising the cap, which stays at 12:

* .templates is now checked out. Most add-ons are thin wrappers around those
  shared scripts, so a large share of reports can only be explained by reading
  them — this makes triage more accurate, not merely faster. 184K, 25 files.
  It has to be added in TWO places: ai_triage_context.sh calls
  `git sparse-checkout set`, which REPLACES the list, so omitting it there
  would silently undo the workflow's checkout at exactly the wrong moment.

* The prompt now states the environment up front: three tools, no Bash, and
  precisely which paths exist on disk. The model cannot discover these cheaply
  — every probe costs a turn it then does not have for the analysis.

Separately, a max-turns death is NOT a workflow fault, but GATE 1 treated every
action failure as systemic and never escalated. So #2949 failed red, stayed
unlabelled, and the catch-up re-dispatched it daily forever — taking the first
of only five slots each time, since it sorts newest-first. It is now handled
like GATE 2: one retry, then ai:needs-human. Detected from the action's
execution_file, which is written even on failure. Warning rather than error,
because a red run per day for a per-issue condition is alarm fatigue, and the
outcome is recorded durably on the issue itself.

The two escalation sites are now one shared function, so they cannot drift.

Re-tested all 15 paths: max-turns across the three events, genuine action
failure with and without an execution file, and the full existing sweep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): make the max-turns probe fail closed on an unexpected file shape

Copilot: hit_max_turns scanned with `.[]?` and no root-type check. jq's `.[]?`
iterates the VALUES of an object, so if the action ever changed the execution
file's shape, {"result":{"subtype":"error_max_turns"}} would have matched —
downgrading a genuine workflow failure from a red run to a warning. That is the
silent-failure class this workflow exists to remove, arriving through the door
I had just built.

Reproduced: with the old filter that object matched; with `(type == "array")`
prepended it does not. Anything that is not the array we expect now falls
through to the loud path.

Verified: the real array shape is still detected and still escalates on the
second look; object-root, nested-object and non-JSON execution files all exit 1
red instead of being swallowed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): fail loudly when escalation doesn't land; don't escalate a manual first look

Two CodeRabbit findings, both reproduced before accepting.

1. escalate_to_human suppressed `gh issue edit` with `|| true`, so it returned
   success even when ai:needs-human never landed. Both callers then exited 0
   reporting a hand-off that had not happened — and, having no label, the issue
   went straight back into the retry rotation the escalation existed to remove.
   The edit now propagates its status and callers exit 1 with an explicit error.
   `gh label create` stays best effort; the edit fails on its own if the label
   is genuinely missing. Verified that removing a label an issue does not carry
   is a no-op, so this cannot fail spuriously.

2. EVENT_NAME was doing duty as an attempt counter, but workflow_dispatch is
   BOTH the daily catch-up retry and the maintainer's manual re-triage — so a
   hand-dispatched FIRST attempt was escalated immediately.

   Rather than the suggested explicit retry state, the two are already
   distinguishable: the catch-up dispatches with GITHUB_TOKEN and arrives as
   github-actions[bot], a manual run as the maintainer. Confirmed against run
   metadata (catch-up 2026-08-10 = github-actions[bot]; manual 2026-07-27 =
   alexbelgium). is_automated_retry() keys on both, which makes "one retry then
   a human" literally true without new persistent state: a manual attempt that
   fails leaves the issue unlabelled, so the catch-up still gets its go.

Re-tested 15 paths including a stubbed `gh` failure at the escalation site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): raise max-turns to 25; no max-turns path may end in a silent green run

Three changes, one requested and two from an independent Codex review.

* --max-turns 12 -> 25, per the maintainer's updated call. The prompt preamble
  and comments were carrying the old number and are updated with it. The
  upfront optimisation stays: the earlier waste was 3 turns retrying an
  unavailable Bash and 6 hunting files outside the sparse checkout, and a
  bigger budget should buy analysis rather than more of that.

* Codex objected that the max-turns branch reintroduced the very failure class
  this workflow exists to prevent. It was right. On the SECOND look the outcome
  is durable (ai:needs-human), but on a FIRST attempt nothing was recorded
  anywhere except an annotation, so exiting 0 was a green run over triage that
  silently did not happen. Now the only exit 0 is the one where the escalation
  label actually landed; every other max-turns path is red. My "alarm fatigue"
  argument was overstated: escalation ends the rotation, so this costs at most
  one red run per problem issue, not one per day.

* Codex also flagged inferring the retry from github.actor as brittle — a
  re-run, a PAT- or App-issued dispatch, or a different maintainer all change
  it, and the false NEGATIVE (an automated retry never recognised as one, so it
  retries forever) is the dangerous direction. Replaced with an explicit
  `source` dispatch input that only the catch-up sets. Unknown provenance is
  now safe by construction because that path ends red rather than green.

Re-tested: max-turns across first look / manual dispatch / catch-up retry /
catch-up-with-failing-label / issue_comment, plus the full existing sweep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(ci): correct two triage comments the recent logic changes left stale

Comments only — no behaviour change, confirmed by diffing out comment lines
(nothing else moved) and re-running the behavioural suite to identical results.

* The prompt preamble still said "the turn budget is 12" and computed
  "leaving 3 for the actual issue" off it. The budget is 25 now. Reworded to
  keep the #2949 evidence, which is still true as history (3 turns retrying
  Bash, 6 hunting files outside the sparse checkout), while stating the
  current budget and why it is not licence to probe more.

* GATE 2 still said "A workflow_dispatch is the catch-up or a manual
  re-triage, i.e. the second look". That stopped being true when escalation
  moved to is_automated_retry(): only source=catchup counts as the second
  attempt, and a manual dispatch is a first look that deliberately does not
  escalate, leaving the issue unlabelled so the catch-up still gets its go.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:55:32 +02:00
github-actions
8cee7c3ea5 Github bot : image compressed 2026-08-09 23:10:26 +00:00
Alexandre
409de579da fix(ci): allow the github-actions bot actor, stop quarantining on systemic failure (#2948)
* fix(ci): allow the github-actions bot actor, stop quarantining on systemic failure

Follow-up to #2947, from watching it run in production. The catch-up dispatched
for the first time (the 403 is gone), but all five dispatched runs then failed:

  Actor type: Bot
  ##[error]Workflow initiated by non-human actor: github-actions (type: Bot).

checkHumanActor (src/github/validation/actor.ts) is a SEPARATE gate from the
write-permission one, and rejects any actor whose account type is not User.
allowed_non_write_users does not cover it — that is only consulted for User
accounts. Switching the catch-up to GITHUB_TOKEN in #2947 made those runs
arrive as github-actions[bot], so it traded the 403 for this.

Fixed with `allowed_bots: "github-actions"` — named rather than "*", since only
this repo's own workflows dispatch as that actor. Scheduled runs are unaffected
either way: they arrive as actor=alexbelgium, a User, which is also why the
tier-2 sweep never hit this.

The same run exposed a design error in #2947's bounded retry. It quarantined an
issue with ai:needs-human when the ACTION failed — but an action failure is
systemic, hitting every issue identically, so a workflow-level fault silently
buried a batch a day. It is the opposite case that is issue-specific: the action
ran fine and the model still produced no usable verdict. Inverted:

  * action failed        -> fail red, touch no labels, let the catch-up retry
  * ran but no verdict   -> one retry, then ai:needs-human on the second look

Five issues (#2847 #2850 #2852 #2896 #2918) were quarantined by the old rule and
need their ai:needs-human removed once this lands, so they re-enter the queue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): allow the coderabbitai bot actor on the PR follow-up tier

Same gate, latent instance. on_pr_coderabbit.yml fires on a review submitted by
coderabbitai[bot], so github.actor is a Bot-type account and checkHumanActor
rejects it. Every run so far skipped on the `ai-fix/*` branch guard before ever
reaching the action, so this has never surfaced — it would have failed on the
first genuine invocation, taking the whole CodeRabbit follow-up tier with it.

Note this is NOT covered by the write-permission check returning early for
[bot] actors: checkHumanActor is a separate gate consulted independently.

Audited all five claude-code-action call sites. The other three need nothing:
on_claude_mention and on_issue_approved are gated to alexbelgium, and the
daily_ai_fix schedule runs as actor=alexbelgium — confirmed from run metadata,
not assumed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): clear retry triggers when escalating to a human; correct a comment

CodeRabbit: the no-verdict escalation added ai:needs-human but left ai-triage
and ai:needs-info in place. The catch-up search excludes both, so the automated
path never reaches it — but a MANUAL re-triage of an already-queued issue does,
and there it matters: ai-triage would keep an issue we just handed to a human
sitting in tier 2's unattended fix queue, and ai:needs-info would let a reporter
reply silently re-trigger classification behind the human's back. The normal
verdict path already clears stale control labels; this makes the escalation
path consistent with it.

Copilot: the action-failure comment claimed "touch no labels", but the
ai:needs-info restore above may already have run on the issue_comment path.
Reworded to say it adds no labels of its own, and why the restore still stands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): check the action outcome before trusting its structured output

CodeRabbit caught the silent-failure mode sneaking back in. The shape check ran
first, so if the action failed AFTER writing a valid structured output, the
object sailed through, labels and a comment were applied, and the step exited 0
— a green run on a failed action, which is the precise thing this workflow was
rebuilt to eliminate. Reproduced: valid verdict + CLASSIFY_OUTCOME=failure
applied bug/ai-triage/ai:classified and posted the comment at exit 0.

A failed action means its output is not trustworthy, full stop, so the outcome
check now runs before the payload is read at all. That also reads better as two
sequential gates rather than nested branches:

  gate 1  action failed        -> restore ai:needs-info, fail red, add nothing
  gate 2  payload unusable     -> restore ai:needs-info, warn, escalate on 2nd look
          otherwise            -> normal verdict handling

The ai:needs-info restore is now a function rather than being repeated at each
exit, since both gates need it.

Re-tested all 12 paths: the two newly-corrected cases plus a full regression
sweep over empty/array/valid payloads across issues, issue_comment and
workflow_dispatch, and the owned / low-confidence / label-grab branches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 20:08:11 +02:00
github-actions[bot]
e8f01387d3 Update stargazer map & cache 2026-08-09 01:01:52 +00:00
Alexandre
409a7366ac Change heading format for support development
Updated heading for support development section.
2026-08-08 23:24:10 +02:00
Alexandre
f3d0980b73 Update README.md 2026-08-08 23:23:45 +02:00
Alexandre
455853cd43 fix(ci): revive AI issue triage — permission gate and catch-up dispatch (#2947)
* fix(ci): revive AI issue triage — permission gate and catch-up dispatch

Tier 1 has been failing on every issue since it went live, while every run
reported success. Two independent causes, both masked:

1. claude-code-action treats `issues` / `issue_comment` as entity contexts
   and runs checkWritePermissions() against github.actor — the outside
   reporter, who never has write. Every Classify step died with "Actor does
   not have write permissions"; continue-on-error painted the job green, and
   Apply verdict found no verdict.json and exited 0. No issue ever got the
   `ai-triage` label, so the tier-2 sweep collected an empty batch nightly
   and there were no automatic fixes either.

   Fixed with `allowed_non_write_users: "*"`, which is the input this case
   exists for. It only takes effect alongside the `github_token` already
   passed. `schedule` / `workflow_dispatch` are automation contexts and skip
   the gate, which is why tiers 2 and 3 were unaffected.

2. The catch-up job dispatched with AI_PR_TOKEN, a fine-grained PAT with no
   actions scope: every dispatch returned 403 and `|| echo :⚠️:`
   swallowed it. Switched to GITHUB_TOKEN with a job-level actions:write —
   workflow_dispatch is exempt from the no-recursion rule, so no PAT is
   needed at all.

Both failures now fail the run instead of reporting success, which is the
part that stops this recurring.

Harden the model's output path, as the action's docs require when the
permission gate is bypassed: drop Bash and GH_TOKEN from the Classify step
(the context script already ran the duplicate search), validate the verdict
enum, cap the comment at 4000 chars, defuse @mentions in it, and accept only
`bug`/`enhancement` as model-supplied labels — the repo also carries
automerge, Priority, codex and wontfix, which a crafted issue body must not
be able to reach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): require the verdict document to be a JSON object

`jq -e .` accepts any truthy JSON, so a verdict of `[1,2]` or `"hi"` passed
the guard and then died on `.verdict` with "Cannot index array with string".
Under set -e that killed the step before the ai:needs-info restore, stranding
the issue so no later reporter reply could re-trigger classification.

Reproduced at exit 5 on an issue_comment event before the fix; the same case
now takes the restore path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ci): drop the Write tool from triage, deliver the verdict via json-schema

Codex review raised a real escalation path. `allowed_non_write_users: "*"`
deliberately admits untrusted reporters, and the model reads their issue body.
It also had a Write tool, so an injected instruction could write a script to
disk and append BASH_ENV=<that script> to the runner's $GITHUB_ENV file command
— discoverable under $RUNNER_TEMP with Glob. The runner applies $GITHUB_ENV
between steps, so the very next bash step (Apply verdict, holding an
issues:write GH_TOKEN) would source it before any validation ran.

Removing Write closes the chain at its source rather than patching a link:
the verdict now comes back through the action's --json-schema structured
output, so the model needs no filesystem write at all and is left with
Read/Glob/Grep. The schema also enforces the verdict and confidence enums and
the two-label cap at the action layer; the shell-side validation stays as
defence in depth.

Apply verdict materialises the structured output through env, never inline
interpolation. issue-classify.md updated to match. All existing behaviour
re-tested through the new path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:55:06 +02:00
Alexandre
e253d18335 fix(binance-trading-bot): unbreak build — pip PEP 668 + v1.0.0 (#2945)
* fix(binance-trading-bot): pip install with --break-system-packages (PEP 668)

Alpine's py3-pip now marks the system Python as externally managed, so the
TradingView requirements install failed with 'externally-managed-environment'
and broke every rebuild (run 31227083872).

Also bumps to upstream v1.0.0, which the updater bot tried and had reverted by
the same build failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(binance-trading-bot): pin base image to the frozen v0 line, drop the v1.0.0 bump

Upstream retagged chrisleekr/binance-trading-bot:latest to v1.0.0 on 2026-07-31
(latest and v1.0.0 share digest sha256:60a1a88e...). build.json pinned :latest,
so this add-on was silently building on the v1 rewrite while its rootfs still
starts mongod/redis and runs the v0 'npm start' entrypoint.

v1.0.0 is a complete rewrite with no in-place upgrade: the datastore moved to
Postgres + TimescaleDB. Adopting it needs an add-on rewrite, not a version bump,
so pin build_from to :0.0.101 (the frozen v0 line, linux/amd64 + linux/arm64)
and pause the updater so the bot stops re-proposing v1 weekly.

Keeps --break-system-packages so the build survives PEP 668 if the pin ever
moves forward.

Reported by Copilot on PR #2945.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(binance-trading-bot): drop --break-system-packages, the pinned image predates it

CI: 'no such option: --break-system-packages'. The 0.0.101 image ships a pip
older than 23.0.1, which is where that flag was introduced.

The flag was only ever needed because :latest had moved to the v1 rewrite and
its newer Alpine carries a PEP 668 marker. With build_from pinned to the frozen
0.0.101 image, that marker cannot appear, so the flag is both unnecessary and
fatal. binance-trading-bot/Dockerfile is now identical to master again: the
whole fix is the build.json pin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:54:51 +02:00
Alexandre
a4b100feea fix(ente): unbreak build — base image moved to ghcr.io/ente/server (#2946)
* fix(ente): base image moved to ghcr.io/ente/server

Upstream renamed the GitHub org ente-io -> ente. github.com redirects, so the
web-builder clone still worked, but GHCR does not redirect: the base image
ghcr.io/ente-io/server:latest now resolves to 'not found' and every rebuild
failed (run 31227202542).

Points the base image and the web source clone at the new org, and lands the
4.4.25 bump the updater bot had reverted by the same failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(ente): sync build.json and README to the renamed org, tidy changelog

build.json still pinned ghcr.io/ente-io/server:927c6a31... — a dead reference
after the org rename (the pinned digest does resolve under ente/server, verified
200 from the registry). It is inert today since this Dockerfile hardcodes its
FROM rather than consuming ARG BUILD_FROM, but leaving a dead ref there is a
trap for the next person.

Also repoints the two README links and matches the changelog date format to the
surrounding entries.

Reported by Copilot on PR #2946.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:54:31 +02:00
GitHub Actions
63d8f6b38e Revert "Updater bot : ente updated to 4.4.25 (upstream 4.4.25)"
This reverts commit ea979365a0.
2026-08-07 23:32:25 +00:00
alexbelgium
e6d8ce0bba Updater bot : transmission_openvpn updated to v5.5.1 (upstream v5.5.1) 2026-08-08 01:31:39 +02:00
alexbelgium
f96c4903cb Updater bot : tdarr updated to 2.86.01 (upstream 2.86.01) 2026-08-08 01:31:31 +02:00
alexbelgium
bb01a71f00 Updater bot : sonarr updated to 4.0.19.2997 (upstream 4.0.19.2997) 2026-08-08 01:31:17 +02:00
alexbelgium
a4d3bd10dc Updater bot : plex updated to 1.43.3.10861.318 (upstream 1.43.3.10861-07dfddaeb-ls318) 2026-08-08 01:30:19 +02:00
alexbelgium
781091ad35 Updater bot : openproject updated to 17.7.1 (upstream 17.7.1) 2026-08-08 01:30:08 +02:00
alexbelgium
b02ed1c135 Updater bot : nzbget updated to v26.2.257 (upstream v26.2-ls257) 2026-08-08 01:30:00 +02:00
alexbelgium
19f297e05d Updater bot : netalertx_fa updated to 26.8.5 (upstream 26.8.5) 2026-08-08 01:29:53 +02:00
alexbelgium
09e77bb359 Updater bot : netalertx updated to 26.8.5 (upstream 26.8.5) 2026-08-08 01:29:48 +02:00
alexbelgium
6f5d30bd8a Updater bot : maintainerr updated to 3.22.0 (upstream 3.22.0) 2026-08-08 01:29:25 +02:00
alexbelgium
74a9f4e0ae Updater bot : jackett updated to 0.24.2342 (upstream 0.24.2342) 2026-08-08 01:29:00 +02:00
alexbelgium
3cebc286de Updater bot : grav updated to 2.0.17 (upstream 2.0.17) 2026-08-08 01:28:31 +02:00
alexbelgium
8cc1de502f Updater bot : flexget updated to 3.20.2 (upstream 3.20.2) 2026-08-08 01:28:16 +02:00
alexbelgium
88f2f13da5 Updater bot : filebrowser_quantum updated to 1.5.1 (upstream 1.5.1) 2026-08-08 01:28:01 +02:00
alexbelgium
48f940018d Updater bot : epicgamesfree updated to 2026.08.04 (upstream debian-2026-08-04) 2026-08-08 01:27:53 +02:00
alexbelgium
ea979365a0 Updater bot : ente updated to 4.4.25 (upstream 4.4.25) 2026-08-08 01:27:49 +02:00
GitHub Actions
38977aa7dd Revert "Updater bot : binance-trading-bot updated to 1.0.0 (upstream 1.0.0)"
This reverts commit 804650fe58.
2026-08-07 23:27:33 +00:00
alexbelgium
b0578b69cb Updater bot : codex updated to 2.2.6 (upstream 2.2.6) 2026-08-08 01:27:26 +02:00
alexbelgium
672be31026 Updater bot : cloudcommander updated to 19.20.0 (upstream 19.20.0) 2026-08-08 01:27:21 +02:00
alexbelgium
16a8e7cf4a Updater bot : cleanuparr updated to 2.10.3 (upstream 2.10.3) 2026-08-08 01:27:17 +02:00
alexbelgium
b8ffd40bab Updater bot : claude_desktop updated to 07308543 (upstream debiantrixie-version-07308543) 2026-08-08 01:27:11 +02:00
alexbelgium
c7fbe3912b Updater bot : calibre updated to 9.13.0 (upstream 9.13.0) 2026-08-08 01:26:41 +02:00
alexbelgium
13b61820bc Updater bot : browserless_chrome updated to 2.55.3 (upstream 2.55.3) 2026-08-08 01:26:37 +02:00
alexbelgium
2e714774a0 Updater bot : browser_brave updated to 1.93.134-ls121 (upstream 1.93.134-ls121) 2026-08-08 01:26:23 +02:00
alexbelgium
804650fe58 Updater bot : binance-trading-bot updated to 1.0.0 (upstream 1.0.0) 2026-08-08 01:25:28 +02:00
alexbelgium
3e37648317 Updater bot : aurral updated to 2.1.0 (upstream 2.1.0) 2026-08-08 01:25:12 +02:00
240 changed files with 4220 additions and 2844 deletions

View File

@@ -25,9 +25,14 @@ Triage first, then one of two paths:
Escalate mid-flight if a light task grows — touches a default, needs a new script or service, or
reveals a deeper problem.
**Standing rule:** ship the simplest solution that works. Complexity is bought only by a
**measurement** showing a concrete, user-visible cost on a real host — never by reasoning about
hypothetical performance.
**Standing rule:** ship the simplest solution that works, and build it out of what already
exists — a `.templates/` module, an existing cont-init script, the pattern a sibling add-on
already uses for the same problem. 120+ add-ons are maintained by one person: a homogeneous repo
where every add-on solves a problem the same way is worth more than a locally nicer bespoke
design. Prefer reusing or extending over adding a parallel implementation, and when you must add
something new, spell it the way the rest of the repo spells it (naming, option names, script
numbering, file layout). Complexity is bought only by a **measurement** showing a concrete,
user-visible cost on a real host — never by reasoning about hypothetical performance.
**Repo layout.** `alexbelgium/hassio-addons`; each add-on is a top-level directory. This skill is
checked in at `.claude/skills/hassio-addon-workflow/` (canonical copy). Set the skill root once,
@@ -73,7 +78,14 @@ before it costs a full analysis pass. Measurement methodology, gotchas, and real
## 3. Plan — choose the mechanism level, then Codex reviews it (full loop)
Rank mechanisms, pick the lowest (simplest) one that solves it, and state the choice in the plan:
Look for prior art first: grep `.templates/` and the other add-ons for something that already
solves this (`grep -rl "<knob or pattern>" --exclude-dir=.git .` — search everything, not just
`*.sh`: the mechanism may live in a `Dockerfile`'s `ARG MODULES=` or an extensionless s6 `run`
file). If an add-on already handles it, the plan is "do what that one does" — say so, and say why
the existing mechanism can't be reused if you're not reusing it.
Then rank mechanisms, pick the lowest (simplest) one that solves it, and state the choice in the
plan:
1. A config value — an option, a schema constraint, an existing env var.
2. An existing knob the base image already reads (`MAX_RES`, `DRINODE`, `SELKIES_*`).
@@ -110,7 +122,13 @@ not just the happy path.
Before requesting review, check: did the diff stay at the ladder level chosen in step 3? Can this
be solved by deleting instead of adding? Is the fix bigger than what it fixes? How does it fail in
three years? Case studies of what happens when this check is skipped: `references/simplify.md`.
three years? And on reuse: does any hunk reimplement something `.templates/`, another script in
this add-on, or a sibling add-on already does — and if a future add-on hits this same problem,
will it find one way to solve it or two? Fold a near-duplicate into the existing mechanism, or
justify the divergence in the PR body — but never at the cost of an isolation rule
`references/traps.md` documents: scripts shared by symlink with the webtop add-ons take a new
numbered script, not an edit. Case studies of what happens when this check is skipped:
`references/simplify.md`.
## 6. Codex attacks the code (full loop only)

View File

@@ -3,17 +3,20 @@
Generate a static PNG world map colour-coded by the percentage of your
stargazers that come from each country. The script maintains a CSV
in ".github/stargazer_countries.csv" cache so that locations are only looked
up once (unless the country entry is blank).
up once. Blank answers are cached too and retried at most every RECHECK_DAYS,
no more than MAX_RECHECKS_PER_RUN re-checks per run.
"""
import csv
import datetime
import math
import os
import sys
import time
from collections import Counter
from pathlib import Path
import plotly.express as px
import plotly.graph_objects as go
import pycountry
import requests
from geopy.geocoders import Nominatim
@@ -25,12 +28,87 @@ GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") # provided by workflow
CSV_PATH = Path(".github/stargazer_countries.csv")
PNG_PATH = Path(".github/stargazer_map.png")
# ---- Cache policy -----------------------------------------------------------
# Most blank rows are permanent: the user simply has no public "location" on
# their profile. Re-asking GitHub and Nominatim for them every week is ~1700
# wasted requests per run, so a blank answer is cached too and only refreshed
# after RECHECK_DAYS. A row with no "last_checked" (i.e. written before the
# column existed) counts as never checked and is looked up once, which
# stamps it.
RECHECK_DAYS = 90
# Cap on how many already-checked rows one run may *re*-check, oldest first.
# It applies only to rows that carry a real last_checked date and have since
# expired: left uncapped, they all fall due on the same day and land as one
# spike. Rows that have never been checked -- new stargazers, and every row
# migrated from the pre-"last_checked" CSV -- are always looked up in full, so
# the first run after this lands still sweeps the whole backlog.
MAX_RECHECKS_PER_RUN = 200
# ---- Rendering theme --------------------------------------------------------
# Dark, opaque panel: GitHub does not swap the image between README themes, so
# a single background has to work in both. A dark canvas with a bright
# sequential ramp stays readable on light and dark pages alike.
BG = "#0d1117" # page / ocean
LAND = "#2b323c" # countries with zero stargazers (still visible)
BORDER = "#0d1117" # country outlines, same as background
FG = "#e6edf3" # primary text
MUTED = "#8b98a5" # secondary text
# Viridis truncated at 35 %: even a single stargazer gets a colour that is
# clearly distinct from the empty-land grey.
SCALE = ["#2c728e", "#21918c", "#35b779", "#90d743", "#fde725"]
# pycountry names that are too long / too formal for a top-5 list
SHORT_NAMES = {
"Russian Federation": "Russia",
"Korea, Republic of": "South Korea",
"Korea, Democratic People's Republic of": "North Korea",
"Iran, Islamic Republic of": "Iran",
"Taiwan, Province of China": "Taiwan",
"Viet Nam": "Vietnam",
"Moldova, Republic of": "Moldova",
"Bolivia, Plurinational State of": "Bolivia",
"Venezuela, Bolivarian Republic of": "Venezuela",
"Tanzania, United Republic of": "Tanzania",
"Syrian Arab Republic": "Syria",
}
HEADERS = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json",
}
GEOL = Nominatim(user_agent="gh-stargazer-map")
# Non-answers that Nominatim happily resolves to a real place: "Earth" is a
# town in Texas, "Remote" is a settlement in Oregon. Matched on the whole
# stripped, lowercased string only -- "Earth, TX" is someone's actual address
# and must still geocode.
JUNK_LOCATIONS = {
"127.0.0.1",
"/dev/null",
"anywhere",
"earth",
"everywhere",
"here",
"home",
"internet",
"localhost",
"mars",
"moon",
"n/a",
"none",
"nowhere",
"null",
"planet earth",
"remote",
"space",
"the internet",
"unknown",
"world",
"worldwide",
}
# -----------------------------------------------------------------------------
@@ -52,20 +130,50 @@ def fetch_stargazer_usernames():
return [s["login"] for s in github_paginated(url)]
def _checked_date(value):
"""Normalise a last_checked cell: a non-ISO-date value reads as never."""
value = (value or "").strip()
try:
datetime.date.fromisoformat(value)
except ValueError:
return ""
return value
def load_cache():
"""Map each username to (country, last_checked). Reads 2- and 3-column CSVs."""
if not CSV_PATH.exists():
return {}
with CSV_PATH.open(newline="", encoding="utf-8") as f:
return {row["username"]: row["country"] for row in csv.DictReader(f)}
return {
row["username"]: (
row["country"],
_checked_date(row.get("last_checked")),
)
for row in csv.DictReader(f)
}
def save_cache(cache):
"""Write the cache back as username,country,last_checked."""
CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
with CSV_PATH.open("w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["username", "country"])
for user, country in sorted(cache.items()):
w.writerow([user, country or ""])
w.writerow(["username", "country", "last_checked"])
for user, (country, last_checked) in sorted(cache.items()):
w.writerow([user, country or "", last_checked])
def needs_lookup(entry, cutoff):
"""True if this entry must be (re)queried. entry is None if absent."""
if entry is None:
return True # new stargazer
country, last_checked = entry
if country:
return False # a known country never changes here
if not last_checked:
return True # blank, never checked (pre-"last_checked" row)
return last_checked < cutoff # blank, and stale enough to retry
def username_to_country(login):
@@ -75,47 +183,223 @@ def username_to_country(login):
loc = (resp.json() or {}).get("location") or ""
if not loc.strip():
return ""
if loc.strip().strip(".!").lower() in JUNK_LOCATIONS:
return ""
try:
g = GEOL.geocode(loc, language="en", timeout=10)
g = GEOL.geocode(loc, language="en", addressdetails=True, timeout=10)
except Exception:
return ""
if not g or "display_name" not in g.raw:
return ""
# take the last comma-separated component that matches a country
for part in reversed(g.raw["display_name"].split(",")):
part = part.strip()
# Use the ISO code from the structured address: Nominatim's English display
# names ("Russia", "Turkey", "Ivory Coast") do not all match pycountry's ISO
# names ("Russian Federation", "Türkiye", "Côte d'Ivoire").
code = ((g.raw.get("address") or {}).get("country_code") or "") if g else ""
country = pycountry.countries.get(alpha_2=code.upper()) if code else None
return country.name if country else ""
def count_by_country(cache):
"""Counter of country name -> stargazers, ignoring blank locations."""
return Counter(country for country, _ in cache.values() if country)
def _log_ticks(lo, hi):
"""Colourbar ticks at ... 0.1, 0.3, 1, 3, 10, 30 ... spanning [lo, hi]."""
candidates = [m * 10**k for k in range(-3, 3) for m in (1, 3)]
ticks = [t for t in candidates if lo / 1.5 <= t <= hi]
return ticks or [hi]
def _fmt_pct(value):
"""1 -> '1%', 0.3 -> '0.3%' -- no trailing zeros."""
return f"{value:.2f}".rstrip("0").rstrip(".") + "%"
def build_figure(counts, total_stargazers):
"""Build the choropleth figure from a {country name: stargazers} mapping."""
by_iso = {}
for name, n in counts.items():
try:
country = pycountry.countries.lookup(part).name
return country
code = pycountry.countries.lookup(name).alpha_3
except LookupError:
pass
return ""
print("Skip unknown country:", name)
continue
# two spellings can resolve to the same ISO code, so accumulate
by_iso[code] = by_iso.get(code, 0) + n
iso = list(by_iso)
vals = [by_iso[k] for k in iso]
# count only what is actually drawn, so the caption matches the map
located = sum(vals) or 1
pcts = [v / located * 100 for v in vals]
lo, hi = (min(pcts), max(pcts)) if pcts else (1.0, 1.0)
def build_choropleth(percent_by_iso):
iso, vals = zip(*percent_by_iso.items())
fig = px.choropleth(
locations=list(iso),
locationmode="ISO-3",
color=list(vals),
color_continuous_scale="Greens",
range_color=(0, max(vals) if vals else 1),
# The distribution is heavily long-tailed (the top country holds ~200x the
# share of the tail), so a linear ramp collapses everything but a handful
# of countries into the first colour step. Colour on log10 of the share.
ticks = _log_ticks(lo, hi)
fig = go.Figure(
go.Choropleth(
locations=iso,
locationmode="ISO-3",
z=[math.log10(p) for p in pcts],
zmin=math.log10(lo) - 0.15, # keep the smallest share off the floor
zmax=math.log10(hi),
colorscale=SCALE,
marker_line_color=BORDER,
marker_line_width=0.5,
colorbar=dict(
title=dict(
text="share of located stargazers (log scale)",
font=dict(color=MUTED, size=13),
side="top",
),
orientation="h",
x=0.52,
y=0.02,
xanchor="center",
yanchor="bottom",
thickness=12,
len=0.34,
outlinewidth=0,
tickvals=[math.log10(t) for t in ticks],
ticktext=[_fmt_pct(t) for t in ticks],
tickfont=dict(color=MUTED, size=12),
),
)
)
fig.update_layout(
coloraxis_colorbar=dict(
title="% stargazers",
orientation="h", # <-- échelle horizontale
x=0.5, # <-- centré
y=0, # <-- tout en bas
xanchor="center",
yanchor="bottom",
thickness=15,
len=0.7, # <-- longueur de l'échelle, ajustable
fig.update_geos(
projection_type="natural earth",
showframe=False,
showcoastlines=False,
showland=True,
landcolor=LAND,
showocean=True,
oceancolor=BG,
showlakes=False,
bgcolor=BG,
lataxis_range=[-56, 84], # crop Antarctica, it is always empty
lonaxis_range=[-176, 186],
domain=dict(x=[0.0, 1.0], y=[0.04, 0.92]),
)
repo = REPO or "this repository"
caption = (
f"{total_stargazers:,} stargazers"
f" | {located:,} mapped to a country"
f" | {len(by_iso)} countries"
)
annotations = [
dict(
text=f"<b>Stargazers of {repo}</b>",
x=0.012,
y=0.985,
xref="paper",
yref="paper",
xanchor="left",
yanchor="top",
showarrow=False,
font=dict(color=FG, size=25),
),
dict(
text=caption,
x=0.012,
y=0.925,
xref="paper",
yref="paper",
xanchor="left",
yanchor="top",
showarrow=False,
font=dict(color=MUTED, size=15),
),
dict(
text="Countries in grey have no located stargazer.<br>"
"Location is read from the public GitHub profile,<br>"
"so the map covers the located subset only.<br>"
"Country lookup by Nominatim geocoding,<br>"
"data © OpenStreetMap contributors.",
x=0.988,
y=0.05,
xref="paper",
yref="paper",
xanchor="right",
yanchor="bottom",
align="right",
showarrow=False,
font=dict(color=MUTED, size=12),
),
]
# Top 5, laid out as two separate annotations (names, share) so each column
# stays aligned whatever the country name length -- HTML text in an SVG
# annotation collapses padding spaces, so a monospace table would not line
# up.
top = counts.most_common(5)
if top:
base_y = 0.40
columns = [
(
0.022,
"left",
"<br>".join(
f"{i}. {SHORT_NAMES.get(name, name)}"
for i, (name, _) in enumerate(top, 1)
),
FG,
),
(
0.215,
"right",
"<br>".join(f"{n / located * 100:.1f}%" for _, n in top),
FG,
),
]
annotations.append(
dict(
text="<b>TOP COUNTRIES</b>",
x=0.022,
y=base_y,
xref="paper",
yref="paper",
xanchor="left",
yanchor="top",
showarrow=False,
font=dict(color=MUTED, size=13),
)
)
annotations += [
dict(
text=text,
x=x,
y=base_y - 0.055,
xref="paper",
yref="paper",
xanchor=anchor,
yanchor="top",
align=anchor,
showarrow=False,
font=dict(color=color, size=15),
)
for x, anchor, text, color in columns
]
fig.update_layout(
width=1240,
height=680,
paper_bgcolor=BG,
plot_bgcolor=BG,
margin=dict(l=0, r=0, t=0, b=0),
annotations=annotations,
)
PNG_PATH.parent.mkdir(parents=True, exist_ok=True)
fig.write_image(str(PNG_PATH), scale=2)
return fig
def build_choropleth(counts, total_stargazers, path=PNG_PATH):
fig = build_figure(counts, total_stargazers)
path.parent.mkdir(parents=True, exist_ok=True)
# 1.5x of 1240x680 -> 1860x1020, sharp on HiDPI at README width without
# committing a multi-megabyte PNG every week.
fig.write_image(str(path), scale=1.5)
def main():
@@ -128,40 +412,46 @@ def main():
cache = load_cache()
# Determine which usernames need a lookup
to_lookup = [u for u in users if cache.get(u, "") == ""]
print(f"Need geocode for {len(to_lookup)} users")
# Determine which usernames need a lookup. Anything never checked -- a new
# stargazer, or a row migrated from the pre-"last_checked" CSV -- is looked
# up in full. Rows that were checked before and have since expired are
# rate-limited to MAX_RECHECKS_PER_RUN, oldest first, so the recurring
# RECHECK_DAYS wave arrives in slices rather than all at once.
now = datetime.date.today()
today = now.isoformat()
cutoff = (now - datetime.timedelta(days=RECHECK_DAYS)).isoformat()
due = [u for u in users if needs_lookup(cache.get(u), cutoff)]
never = [u for u in due if not cache.get(u, ("", ""))[1]]
expired = sorted(
(u for u in due if cache.get(u, ("", ""))[1]),
key=lambda u: (cache[u][1], u),
)
rechecks = expired[:MAX_RECHECKS_PER_RUN]
to_lookup = never + rechecks
print(
f"Need geocode for {len(to_lookup)} users "
f"({len(never)} never checked, {len(rechecks)} of {len(expired)} expired)"
)
for i, login in enumerate(to_lookup, 1):
country = username_to_country(login)
cache[login] = country
cache[login] = (country, today)
print(f"{i}/{len(to_lookup)}: {login:<20} -> {country}")
# Nominatim polite usage
time.sleep(1)
# Ensure all stargazers are in cache (even those with blank location)
for u in users:
cache.setdefault(u, "")
cache.setdefault(u, ("", today))
save_cache(cache)
# Build stats
countries = [c for c in cache.values() if c]
counts = Counter(countries)
total = sum(counts.values()) or 1
pct_by_country = {c: v / total for c, v in counts.items()}
# convert to ISO-3 for plotly
pct_by_iso = {}
for c, pct in pct_by_country.items():
try:
iso = pycountry.countries.lookup(c).alpha_3
pct_by_iso[iso] = pct * 100 # plotly wants numeric
except LookupError:
print("Skip unknown country:", c)
# The cache is never pruned, so it still holds users who have since
# unstarred. Keep them for future geocoding, but render only current stars.
counts = count_by_country({u: cache[u] for u in users})
print("Rendering PNG map…")
build_choropleth(pct_by_iso)
build_choropleth(counts, len(users))
print(
"Done – files saved:",
CSV_PATH.relative_to("."),

View File

@@ -5,8 +5,9 @@ You are triaging a new issue on `alexbelgium/hassio-addons`, a monorepo of
`run.sh`, s6 services, nginx config, `config.yaml`) around an upstream
application that Alex does not maintain.
Your entire output is one JSON object written to `/tmp/ai-triage/verdict.json`.
You do not comment, label, or edit anything.
Your entire output is one JSON object, returned as the run's structured output
and matching the schema below. You have read-only tools by design: you do not
comment, label, write files, or edit anything.
## Rule 0 — ownership short-circuit
@@ -97,6 +98,13 @@ Never close an issue. Never promise a timeline. Never say a fix is coming.
}
```
`labels` should contain at most two, from the repo's existing set. Do not
invent new label names; the workflow adds `ai-triage` and `ai:classified`
on its own.
Only `verdict` and `confidence` are required; omit the rest when they do not
apply.
`labels` is cosmetic and accepts only `bug` or `enhancement`, at most two —
the workflow discards anything else, so inventing a label name simply loses
it. Control labels are not yours to set: the workflow adds `ai-triage`,
`ai:classified`, `ai:needs-info` and `ai:needs-human` on its own.
`comment` must stay under 4000 characters; a longer one is discarded and the
issue is handed to a human instead.

View File

@@ -77,7 +77,10 @@ if [ -n "$ADDON" ]; then
{
echo
echo "## Addon files: ${ADDON}/"
if ! git sparse-checkout set --no-cone .github/prompts .github/scripts "$ADDON" 2>&1; then
# `set` REPLACES the checkout list, so .templates has to be repeated here
# or the workflow's sparse-checkout of it is silently undone at this point
# — which is exactly the state that starved #2949 of its turn budget.
if ! git sparse-checkout set --no-cone .github/prompts .github/scripts .templates "$ADDON" 2>&1; then
# Swallowing this used to leave ADDON resolved with no files behind it,
# so the classifier could still reach high confidence off the addon
# name alone. Say so explicitly, in the same word Rule 2 already keys

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 404 KiB

BIN
.github/stats.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -125,7 +125,7 @@ jobs:
- name: Analyse and fix
if: steps.batch.outputs.count != '0'
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange. The scheduled path

View File

@@ -64,7 +64,7 @@ jobs:
fetch-depth: 1
- name: Run Claude Code
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.

View File

@@ -135,7 +135,7 @@ jobs:
- name: Execute the plan
if: steps.bundle.outputs.has_plan == 'true'
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever

View File

@@ -21,6 +21,10 @@
#
# Auth: Claude Pro/Max subscription via the CR_PAT GitHub Environment, which
# holds the CLAUDE_CODE_OAUTH_TOKEN secret (generate with `claude setup-token`).
# GitHub side is GITHUB_TOKEN throughout — no PAT. The classify job pairs it
# with `allowed_non_write_users` so an outside reporter's issue-open event can
# get past the action's write-permission gate; the catch-up job pairs it with a
# job-level actions:write so it can dispatch. See the comments at each site.
name: AI issue triage
@@ -37,6 +41,16 @@ on:
issue:
description: "Issue number to (re-)triage manually"
required: true
source:
# Explicit provenance, set only by the catch-up job below. Previously
# this was inferred from github.actor, which is brittle: a re-run, a
# dispatch via a PAT or App, or another maintainer all change it, and
# the dangerous direction is the false negative — an automated retry
# that is never recognised as one keeps retrying forever. An input the
# scheduler sets explicitly cannot drift with GitHub's actor semantics.
description: "Set to 'catchup' by the daily catch-up job; leave blank for a manual re-triage"
required: false
default: ""
permissions:
contents: read
@@ -127,9 +141,17 @@ jobs:
with:
fetch-depth: 1
persist-credentials: false
# .templates holds the shared build/runtime scripts (ha_entrypoint.sh,
# ha_automodules.sh, the cont-init modules) that nearly every add-on
# depends on, so a large share of reports can only be explained by
# reading them. Without it the classifier burned 6 of its turns on
# #2949 hunting for files that were not checked out, then died on
# max_turns. It is a small directory — cheaper to ship than to search
# for and not find.
sparse-checkout: |
.github/prompts
.github/scripts
.templates
sparse-checkout-cone-mode: false
- name: Build context bundle
@@ -144,7 +166,7 @@ jobs:
id: classify
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
continue-on-error: true
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Without this the action falls back to the OIDC -> Claude App token
@@ -153,21 +175,99 @@ jobs:
# opened the issue or replied to a needs-info request. Same token the
# step already exports as GH_TOKEN; classify only reads.
github_token: ${{ secrets.GITHUB_TOKEN }}
# THE fix for tier 1. `issues` and `issue_comment` are "entity"
# contexts in the action (src/github/context.ts), so it runs
# checkWritePermissions() against github.actor — which on an
# issue-open event is the outside reporter, who never has write.
# Every run failed there ("Actor does not have write permissions")
# and continue-on-error painted it green. The bypass branch in
# src/github/validation/permissions.ts needs BOTH github_token
# (above) and a non-empty allowed_non_write_users — hence this.
# `schedule` / `workflow_dispatch` are "automation" contexts and skip
# the check entirely, which is why the catch-up path below does not
# need it.
#
# This is the case the input exists for (docs/security.md: "designed
# for automation workflows where user permissions are already
# restricted by the workflow's permission scope"). The scope here is
# contents:read + issues:write, the model gets no credentials and no
# Bash, and every value it produces is validated in Apply verdict.
allowed_non_write_users: "*"
# Separate gate from the one above, and it bit the catch-up path in
# production: checkHumanActor (src/github/validation/actor.ts)
# rejects any actor whose account type is not User. The catch-up
# dispatches with GITHUB_TOKEN, so those runs arrive as
# github-actions[bot] and died with "Workflow initiated by non-human
# actor". allowed_non_write_users does NOT cover this — it is only
# consulted for User accounts.
# Named rather than "*": only this repo's own workflows can dispatch
# as github-actions, whereas "*" would also admit any other App that
# can reach a trigger. The matcher lowercases and strips a trailing
# [bot], so this entry matches the github-actions[bot] actor.
# Scheduled runs are unaffected either way — they arrive as
# actor=alexbelgium, a User.
allowed_bots: "github-actions"
show_full_output: true
# Stated up front, because a wrong guess about the environment costs
# turns the analysis then does not have. On #2949, under the earlier
# 12-turn budget, the model spent 3 turns retrying Bash and 6 hunting
# files outside the sparse checkout and died before reaching a
# verdict. The budget is 25 now, but it is meant to buy analysis, not
# more failed probing — keep this in step with --max-turns below.
prompt: |
Read /tmp/ai-triage/context.md, then follow the instructions in
.github/prompts/issue-classify.md exactly.
Write your verdict as a single JSON object to
/tmp/ai-triage/verdict.json and write nothing else anywhere.
Do NOT comment on or label the issue yourself.
Before you start, two facts about this environment. Both are hard
limits, not preferences — working around them is not possible and
costs you turns you need for the analysis.
You have exactly three tools: Read, Glob and Grep. There is no
Bash. Do not try to run `find`, `ls`, `cat` or any other command;
those calls fail and are not retryable. Use Glob where you would
have used `find`, and Grep where you would have used `grep`.
This is a SPARSE checkout of a 100+ add-on monorepo. Only these
paths exist on disk — everything else is absent, and searching for
it will find nothing no matter how you phrase the search:
* .templates/ shared build and runtime scripts that most
add-ons rely on (ha_entrypoint.sh,
ha_automodules.sh, the cont-init modules)
* .github/prompts/, .github/scripts/
* the single add-on directory named in the context bundle, if it
was resolved — the bundle says which, or says UNRESOLVED
Other add-ons are NOT present. If the bundle says UNRESOLVED, no
add-on source is on disk at all: judge from the bundle alone and
set confidence accordingly rather than searching for the code.
You have a budget of 25 turns. The context bundle already contains
the issue, its comments, the add-on's config/Dockerfile/docs, its
recent commits and candidate duplicates — so read it first and
spend turns only on what it does not already answer.
Return your verdict as structured output. Do NOT comment on or
label the issue yourself.
# The model gets NO write capability of any kind — not Bash, not
# Write, and no GH_TOKEN in this step's env. That matters more here
# than usual: allowed_non_write_users above deliberately admits
# untrusted reporters, and the issue body it reads is their text.
# With a Write tool an injected instruction could drop a script on
# disk and append BASH_ENV=<that script> to the runner's $GITHUB_ENV
# file command (discoverable under $RUNNER_TEMP with Glob). The
# runner applies that between steps, so the next bash step — Apply
# verdict, holding an issues:write GH_TOKEN — would source it before
# any of the validation below ran. Delivering the verdict through the
# action's --json-schema structured output instead of a file removes
# the write primitive that chain starts from.
# Duplicate lookup is already done too: ai_triage_context.sh ran
# `gh search issues` and baked the candidates into context.md, so the
# model has nothing left to ask GitHub for either.
claude_args: |
--model claude-sonnet-5
--effort low
--max-turns 12
--allowedTools "Read,Write,Glob,Grep,Bash(gh issue list:*),Bash(gh search issues:*)"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--max-turns 25
--allowedTools "Read,Glob,Grep"
--json-schema '{"type":"object","properties":{"verdict":{"type":"string","enum":["owned","duplicate","needs-info","question","upstream-bug","addon-bug","feature-request"]},"addon":{"type":"string"},"confidence":{"type":"string","enum":["high","medium","low"]},"duplicate_of":{"type":"integer"},"labels":{"type":"array","items":{"type":"string"},"maxItems":2},"root_cause_hint":{"type":"string"},"comment":{"type":"string"}},"required":["verdict","confidence"]}'
- name: Apply verdict
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
@@ -176,31 +276,216 @@ jobs:
ISSUE: ${{ github.event.issue.number || inputs.issue }}
REPO: ${{ github.repository }}
EVENT_NAME: ${{ github.event_name }}
# Distinguishes the automated catch-up retry from a manual
# re-triage — see is_automated_retry below.
DISPATCH_SOURCE: ${{ inputs.source }}
CLASSIFY_OUTCOME: ${{ steps.classify.outcome }}
# Through env, never interpolated into the script body: this string
# is model output and "${{ }}" inline would splice it into the shell
# source itself.
STRUCTURED: ${{ steps.classify.outputs.structured_output }}
# Written by the action even when it fails (setExecutionFileOutputIfPresent
# runs in its catch block), which is what lets the max-turns check below
# work on exactly the runs that need it.
EXECUTION_FILE: ${{ steps.classify.outputs.execution_file }}
run: |
set -euo pipefail
mkdir -p /tmp/ai-triage
F=/tmp/ai-triage/verdict.json
if [ ! -s "$F" ] || ! jq -e . "$F" >/dev/null 2>&1; then
echo "::warning::no usable verdict produced, leaving issue untouched"
# A reporter reply consumed ai:needs-info in the claim step above.
# With no verdict we would otherwise leave the issue with the flag
# gone, so the next reply could never re-trigger — restore it.
if [ "${EVENT_NAME:-}" = "issue_comment" ]; then
gh issue edit "$ISSUE" --repo "$REPO" --add-label ai:needs-info >/dev/null 2>&1 || true
# A reporter reply consumed ai:needs-info in the claim step above, so
# every early exit below has to restore it or the next reply could
# never re-trigger. Defined once here rather than repeated per exit.
restore_needs_info() {
[ "${EVENT_NAME:-}" = "issue_comment" ] || return 0
gh issue edit "$ISSUE" --repo "$REPO" --add-label ai:needs-info >/dev/null 2>&1 || true
}
# Is this the automated second look, rather than a first attempt?
# EVENT_NAME alone is not enough: workflow_dispatch is BOTH the daily
# catch-up retry and the maintainer's manual re-triage, so keying on
# it alone escalates a hand-dispatched first attempt immediately.
# The catch-up therefore states its provenance explicitly via the
# `source` input. Inferring it from github.actor instead was rejected:
# a re-run, a PAT- or App-issued dispatch, or a different maintainer
# all change the actor, and the failure that matters is the false
# NEGATIVE — an automated retry not recognised as one would never
# escalate and would retry that issue forever.
# Unknown provenance is treated as "not the automated retry", which
# is safe here because every non-escalating max-turns path below ends
# in a red run rather than a silent green one.
is_automated_retry() {
[ "${EVENT_NAME:-}" = "workflow_dispatch" ] && [ "${DISPATCH_SOURCE:-}" = "catchup" ]
}
# Hand the issue to a human and take it out of the retry rotation.
# Returns non-zero if the labels did not actually land — callers must
# treat that as a failure rather than reporting a hand-off that never
# happened, which would leave the issue unlabelled and back in the
# retry rotation it was supposed to leave.
escalate_to_human() {
# Best effort: the label usually exists, and `gh issue edit` fails
# on its own below if it does not.
gh label create ai:needs-human --repo "$REPO" --color ededed >/dev/null 2>&1 || true
# NOT suppressed with `|| true`. ai-triage and ai:needs-info come
# off in the same call: leaving ai-triage would keep an issue we
# just escalated sitting in tier 2's unattended queue, and leaving
# ai:needs-info would let a reporter reply silently re-trigger
# classification behind the human's back. Removing a label the
# issue does not carry is a no-op, so this cannot fail spuriously.
gh issue edit "$ISSUE" --repo "$REPO" \
--add-label ai:needs-human \
--remove-label ai-triage --remove-label ai:needs-info >/dev/null 2>&1
}
# Did the run die on its turn budget rather than on a workflow fault?
# The execution file is a JSON array of SDK messages; the terminal
# result object carries subtype "error_max_turns".
#
# This MUST fail closed: a false positive here downgrades a genuine
# workflow failure from a red run to a warning, which is the exact
# silent-failure class this workflow was rebuilt to remove. Hence the
# explicit `type == "array"` root check — without it `.[]?` happily
# iterates the VALUES of an object, so if the action ever changed the
# file's shape, {"result":{"subtype":"error_max_turns"}} would match
# and mask the failure. Anything that is not the array we expect is
# treated as "not max turns" and falls through to the loud path.
# The `?` and per-element type check keep a non-object element from
# aborting the step under set -e.
hit_max_turns() {
[ -n "${EXECUTION_FILE:-}" ] && [ -s "${EXECUTION_FILE:-}" ] || return 1
jq -e '(type == "array") and
any(.[]?;
(type == "object") and
(((.subtype? // "") == "error_max_turns") or
((.terminal_reason? // "") == "max_turns")))' \
"$EXECUTION_FILE" >/dev/null 2>&1
}
# GATE 1 — did the action itself run? This is checked BEFORE looking
# at the payload, because the action can fail *after* having written
# a valid structured output: the object would sail through the shape
# check below, labels and a comment would be applied, and the step
# would exit 0 — a green run on a failed action, which is the exact
# silent-failure mode this workflow was rebuilt to eliminate.
# A failed action means its output is not trustworthy, full stop.
#
# This branch is also deliberately label-neutral. An action failure
# (auth, config, an outage) is systemic — it hits every issue the
# same way — so quarantining here would silently bury a batch a day
# while the real fault sits in the workflow. Fail red, add nothing,
# let the catch-up retry once it's fixed. Classify carries
# continue-on-error so this step still runs at all; without the
# explicit exit 1 the job would report success.
if [ "${CLASSIFY_OUTCOME:-}" = "failure" ]; then
restore_needs_info
# ...with one exception. Exhausting the turn budget is NOT a
# workflow fault: the action ran fine and this particular issue was
# just too tangled to finish inside the turn budget. Treating it as systemic
# meant #2949 failed red and stayed unlabelled, so the catch-up
# re-dispatched it every day forever — and being the newest issue
# it took the first of only five daily slots each time.
# So it is handled like GATE 2 below instead: one retry, then a
# human. Warning rather than error, because a red run per day for a
# per-issue condition is alarm fatigue, and the outcome is recorded
# durably on the issue itself rather than only in a run log.
# A green run is only ever justified once the outcome is recorded
# somewhere durable. On the automated second look that is the
# ai:needs-human label, and only if it actually landed. On a first
# attempt nothing is recorded anywhere but this annotation, so
# exiting 0 there would be precisely the "green run, work silently
# dead" state that left triage broken for weeks. It costs at most
# one red run per problem issue, not one per day, because the
# second look ends the retry rotation either way.
if hit_max_turns; then
if is_automated_retry; then
echo "::warning::second attempt for #$ISSUE also ran out of turns, handing it to a human"
if ! escalate_to_human; then
echo "::error::could not label #$ISSUE ai:needs-human — it is NOT escalated and stays in the retry rotation"
exit 1
fi
exit 0
fi
echo "::error::classification for #$ISSUE ran out of turns; leaving it for the catch-up to retry once, after which it goes to a human"
exit 1
fi
echo "::error::the Classify action failed for #$ISSUE — this is usually a workflow-level fault affecting every issue, so the issue is left untouched for a retry. See the Classify step."
exit 1
fi
# The verdict arrives as the action's schema-validated structured
# output rather than a file the model wrote — see the Classify step.
printf '%s' "${STRUCTURED:-}" > "$F"
# GATE 2 — the action ran, but is the payload usable? `jq -e .` alone
# accepts any truthy JSON, so a verdict of `[1,2]` or `"hi"` would
# pass and then die on `.verdict` below with "Cannot index array with
# string", killing the step under set -e before the restore. Require
# an object.
#
# Reaching here means the failure is specific to THIS issue — the
# model looked at it and produced nothing usable — so a retry is
# worth exactly one attempt. Only a dispatch carrying source=catchup
# counts as that second attempt (is_automated_retry above); a manual
# workflow_dispatch is a first look and does NOT escalate, leaving
# the issue unlabelled so the catch-up still gets its own go. On the
# automated retry, hand it to a human rather than re-dispatching the
# same issue every day forever; ai:needs-human is in the catch-up
# exclusion search, so it drops out of the queue instead of starving
# newer issues behind it.
if [ ! -s "$F" ] || ! jq -e 'type == "object"' "$F" >/dev/null 2>&1; then
restore_needs_info
echo "::warning::no usable verdict produced for #$ISSUE"
if is_automated_retry; then
echo "::warning::second attempt produced no verdict, handing #$ISSUE to a human"
if ! escalate_to_human; then
echo "::error::could not label #$ISSUE ai:needs-human — it is NOT escalated and stays in the retry rotation"
exit 1
fi
fi
exit 0
fi
echo "--- verdict ---"; jq . "$F"; echo "---------------"
# Everything below is derived from a file the model wrote after
# reading an attacker-controlled issue body, so treat all of it as
# untrusted input and validate before it reaches a `gh` call.
VERDICT=$(jq -r '.verdict // "unknown"' "$F")
CONF=$(jq -r '.confidence // "low"' "$F")
COMMENT=$(jq -r '.comment // ""' "$F")
# Model-supplied labels are cosmetic only (e.g. "bug"). ai-triage /
# ai:classified / ai:needs-human are workflow-owned control labels;
# strip anything in that namespace so a verdict can't self-trigger
# tier 2 (the deterministic add below is the only legitimate source
# of ai-triage).
mapfile -t LABELS < <(jq -r '.labels[]? // empty' "$F" | grep -vE '^ai[:-]' || true)
case "$VERDICT" in
owned|duplicate|needs-info|question|upstream-bug|addon-bug|feature-request) ;;
*) echo "::warning::unrecognised verdict '$VERDICT', treating as low confidence"
VERDICT="unknown"; CONF="low" ;;
esac
case "$CONF" in high|medium|low) ;; *) CONF="low" ;; esac
# A triage comment is a duplicate one-liner or a <=4-item checklist.
# Anything longer is a malfunction or an attempt to use the bot's
# identity to post a wall of text / mention spam, so cap it.
if [ "${#COMMENT}" -gt 4000 ]; then
echo "::warning::comment was ${#COMMENT} chars, suppressing it and flagging a human"
COMMENT=""; CONF="low"
fi
# Model-supplied labels are cosmetic only, so this is an explicit
# allowlist rather than "any existing label that isn't ai:*". The repo
# carries labels that steer things — automerge, Priority, codex,
# wontfix, dependency-update, no-ai — and a crafted issue body must not
# be able to reach any of them through the classifier. These two are
# the only ones tier 1's verdicts actually map onto (addon-bug /
# upstream-bug -> bug, feature-request -> enhancement); both already
# exist, so nothing is ever created from model output. Cap at 2, as
# issue-classify.md already specifies.
mapfile -t LABELS < <(
jq -r '.labels[]? // empty' "$F" \
| grep -xE 'bug|enhancement' \
| head -n 2 || true
)
# Someone already owns this one: ping_submitter did its job. Best-
# effort clear of a manual re-triage's stale control labels (e.g. a
@@ -238,16 +523,23 @@ jobs:
fi
LABELS+=("ai:classified")
# No --force: an existing label (e.g. a model-supplied cosmetic
# "bug") must be left as-is. --force would update it, recoloring
# every such label to ededed as a side effect of triage. Without it,
# create fails harmlessly on labels that already exist (|| true),
# and still creates the workflow-owned ones the first time.
for l in "${LABELS[@]}"; do
# Only the workflow-owned control labels are ever created here; the
# cosmetic ones were already filtered down to labels that exist. No
# --force, so an existing label keeps its colour instead of being
# recoloured to ededed as a side effect of triage.
for l in ai-triage ai:classified ai:needs-human ai:needs-info; do
gh label create "$l" --repo "$REPO" --color ededed >/dev/null 2>&1 || true
done
gh issue edit "$ISSUE" --repo "$REPO" \
"${LABELS[@]/#/--add-label=}"
# LABELS always picks up ai:classified above, so it cannot be empty
# today — but an empty array would expand to zero arguments and make
# `gh issue edit` fail with no option supplied, killing the step under
# set -e. Guard it so a future branch can't reintroduce that.
if [ "${#LABELS[@]}" -gt 0 ]; then
gh issue edit "$ISSUE" --repo "$REPO" \
"${LABELS[@]/#/--add-label=}"
else
echo "::warning::no labels selected, skipping the add"
fi
# Manual re-triage can flip the verdict (e.g. a prior addon-bug
# re-run now comes back needs-info/upstream-bug): clear whichever
@@ -265,6 +557,13 @@ jobs:
fi
if [ -n "$COMMENT" ]; then
# The comment body is model prose written after reading an
# attacker-controlled issue. Defuse @mentions in it so a crafted
# issue can't turn the bot into a notification cannon: the empty
# HTML comment stops GitHub linkifying (and notifying) the handle
# while still rendering as plain "@name". The footer's own mention
# of the maintainer is added below, after this, so it still works.
COMMENT=$(printf '%s' "$COMMENT" | sed 's/@\([A-Za-z0-9]\)/@<!-- -->\1/g')
{
printf '%s\n\n' "$COMMENT"
printf -- '---\n'
@@ -283,13 +582,26 @@ jobs:
if: ${{ github.event_name == 'schedule' && vars.AI_DISABLED != 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 10
environment: CR_PAT
# No `environment: CR_PAT` — this job holds no Claude call and now uses
# GITHUB_TOKEN, so it needs nothing from that environment's secrets.
# Job-level, so only this job gets actions:write — the classify job above
# keeps the workflow-level contents:read + issues:write, which is what
# allowed_non_write_users is safe under.
permissions:
contents: read
issues: read
actions: write
steps:
- name: Re-dispatch untriaged issues
env:
# AI_PR_TOKEN (repo scope) can dispatch workflows; GITHUB_TOKEN would
# need actions:write added to the whole workflow.
GH_TOKEN: ${{ secrets.AI_PR_TOKEN }}
# Was secrets.AI_PR_TOKEN, which is a fine-grained PAT WITHOUT the
# actions scope: every dispatch returned "HTTP 403: Resource not
# accessible by personal access token" and the `|| echo ::warning::`
# below swallowed it, so the safety net never caught anything.
# GITHUB_TOKEN + the job-level actions:write above needs no PAT at
# all, and workflow_dispatch is explicitly exempt from the rule that
# GITHUB_TOKEN-triggered events don't start new runs.
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
@@ -313,9 +625,20 @@ jobs:
COUNT=$(grep -c . /tmp/todo.txt || true)
echo "untriaged issues to re-dispatch: $COUNT"
FAILED=0
while IFS= read -r n; do
[ -n "$n" ] || continue
echo "re-dispatching tier 1 for #$n"
gh workflow run "AI issue triage" --repo "$REPO" -f issue="$n" || \
echo "::warning::could not dispatch classify for #$n"
gh workflow run "AI issue triage" --repo "$REPO" -f issue="$n" -f source=catchup || {
echo "::error::could not dispatch classify for #$n"
FAILED=$((FAILED + 1))
}
done < /tmp/todo.txt
# This job IS the safety net. A net that fails silently is worse than
# no net — it reported success every day for weeks while dispatching
# nothing. Fail the run so the breakage is visible.
if [ "$FAILED" -gt 0 ]; then
echo "::error::$FAILED of $COUNT catch-up dispatches failed"
exit 1
fi

View File

@@ -79,7 +79,7 @@ jobs:
- name: Address CodeRabbit comments
if: steps.claim.outputs.go == 'true'
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
uses: anthropics/claude-code-action@1623c36729ac1cd5895198cded705a287de7db79 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Skip the OIDC -> Claude App token exchange, which 401s whenever
@@ -87,6 +87,17 @@ jobs:
# coderabbitai[bot], the review submitter. AI_PR_TOKEN, not
# GITHUB_TOKEN, so the pushed fixes re-trigger CI on the PR.
github_token: ${{ secrets.AI_PR_TOKEN }}
# Latent until now only because this job has never reached the action:
# every run so far skipped on the `ai-fix/*` branch guard. On the
# first real firing github.actor is coderabbitai[bot], and
# checkHumanActor (src/github/validation/actor.ts) rejects any actor
# whose account type is not User — a different gate from the write
# check above, which does return early for a [bot] actor. Without
# this the whole CodeRabbit follow-up tier would fail on its first
# genuine invocation. Named, not "*": the job `if` already requires
# the review to come from coderabbitai[bot], so nothing else can get
# here anyway, and "*" would only widen it if that guard changed.
allowed_bots: "coderabbitai"
prompt: |
CodeRabbit has reviewed pull request #${{ github.event.pull_request.number }}
on ${{ github.repository }}. You are on that PR's branch. Follow

View File

@@ -2,7 +2,7 @@
<!-- markdownlint-disable MD033 -->
## 💖 Support development
## Support development
I maintain this and other Home Assistant add-ons in my free time: keeping up with upstream changes, HA changes, and testing on real hardware takes a lot of time (and some money). I use around 5-10 of my >110 addons so regularly I install test machines (and purchase some test services such as vpn) that I don't use myself to troubleshoot and improve the addons
@@ -70,9 +70,15 @@ If you want to do add the repository manually, please follow the procedure highl
- %%STATS_AMD64%%
- %%STATS_AARCH64%%
### Stars evolution
### Star History
[![Star History Chart](https://api.star-history.com/svg?repos=alexbelgium/hassio-addons&type=Date)](https://star-history.com/#alexbelgium/hassio-addons&Date)
<a href="https://www.star-history.com/?type=date&repos=alexbelgium%2Fhassio-addons">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&theme=dark&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
</picture>
</a>
## Add-ons provided by this repository

View File

@@ -2,7 +2,7 @@
<!-- markdownlint-disable MD033 -->
## 💖 Support development
## Support development
I maintain this and other Home Assistant add-ons in my free time: keeping up with upstream changes, HA changes, and testing on real hardware takes a lot of time (and some money). I use around 5-10 of my >110 addons so regularly I install test machines (and purchase some test services such as vpn) that I don't use myself to troubleshoot and improve the addons
@@ -70,9 +70,15 @@ If you want to do add the repository manually, please follow the procedure highl
- amd64: 93%
- aarch64: 7%
### Stars evolution
### Star History
[![Star History Chart](https://api.star-history.com/svg?repos=alexbelgium/hassio-addons&type=Date)](https://star-history.com/#alexbelgium/hassio-addons&Date)
<a href="https://www.star-history.com/?type=date&repos=alexbelgium%2Fhassio-addons">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&theme=dark&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=alexbelgium/hassio-addons&type=date&legend=top-left&sealed_token=Ft6D4rx2V8l-M626J7uFACNWFJexZTQuLZvFi-nQ_FnbQ0KFnkzPBnnQdui7CREsxlWJ5rdTXvx5PVjpFxxQwump2HCc5SDviHt_iZPdJB3ckWEjXp0V3w" />
</picture>
</a>
## Add-ons provided by this repository

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,4 +1,7 @@
## 2.1.0 (2026-08-08)
- Update to latest version from lklynet/aurral (changelog : https://github.com/lklynet/aurral/releases)
## 2.0.3 (2026-08-01)
- Update to latest version from lklynet/aurral (changelog : https://github.com/lklynet/aurral/releases)

View File

@@ -1,5 +1,5 @@
name: Aurral
version: "2.0.3"
version: "2.1.0"
slug: aurral
description: >-
Self-hosted music discovery, request management, flows, and playlist

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,9 +1,9 @@
{
"last_update": "2026-08-01",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "aurral",
"source": "github",
"upstream_repo": "lklynet/aurral",
"upstream_version": "2.0.3",
"upstream_version": "2.1.0",
"github_beta": false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,8 @@
## 0.0.101.1 (2026-08-08)
- Fix broken builds: upstream retagged `:latest` to the v1.0.0 rewrite on 2026-07-31, so this add-on was building on an image its rootfs does not support. `build_from` is now pinned to `chrisleekr/binance-trading-bot:0.0.101`, the frozen v0 line this add-on targets.
- This also resolves the `externally-managed-environment` (PEP 668) pip failure, which was a symptom of the same retag: the v1 image ships a much newer Alpine than the v0 line this add-on is built against.
- Upstream tracking is paused: v1.0.0 is a complete rewrite with no in-place upgrade (datastore moved to Postgres + TimescaleDB), so it needs an add-on rewrite rather than a version bump.
- Added support for configuring extra environment variables via the `env_vars` add-on option alongside config.yaml. See https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2 for details.
## 0.0.101 (2025-06-13)

View File

@@ -1,6 +1,6 @@
{
"build_from": {
"aarch64": "chrisleekr/binance-trading-bot:latest",
"amd64": "chrisleekr/binance-trading-bot:latest"
"aarch64": "chrisleekr/binance-trading-bot:0.0.101",
"amd64": "chrisleekr/binance-trading-bot:0.0.101"
}
}

View File

@@ -115,4 +115,4 @@ schema:
slug: binance-trading-bot
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: 0.0.101
version: 0.0.101.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,6 +1,7 @@
{
"github_beta": "true",
"last_update": "13-06-2025",
"last_update": "08-08-2026",
"paused": true,
"repository": "alexbelgium/hassio-addons",
"slug": "binance-trading-bot",
"source": "github",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,4 +1,7 @@
## 1.93.134-ls121 (2026-08-08)
- Update to latest version from linuxserver/docker-brave (changelog : https://github.com/linuxserver/docker-brave/releases)
## 1.93.129-ls119 (2026-08-01)
- Update to latest version from linuxserver/docker-brave (changelog : https://github.com/linuxserver/docker-brave/releases)

View File

@@ -69,5 +69,5 @@ slug: brave
tmpfs: true
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "1.93.129-ls119"
version: "1.93.134-ls121"
video: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,9 +1,9 @@
{
"github_fulltag": "true",
"last_update": "2026-08-01",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "brave",
"source": "github",
"upstream_repo": "linuxserver/docker-brave",
"upstream_version": "1.93.129-ls119"
"upstream_version": "1.93.134-ls121"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,4 +1,7 @@
## 2.55.3 (2026-08-08)
- Update to latest version from browserless/chrome (changelog : https://github.com/browserless/chrome/releases)
## 2.55.2 (2026-08-01)
- Update to latest version from browserless/chrome (changelog : https://github.com/browserless/chrome/releases)

View File

@@ -86,5 +86,5 @@ schema:
slug: browserless_chrome
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/browserless_chrome
version: "2.55.2"
version: "2.55.3"
webui: "[PROTO:ssl]://[HOST]:[PORT:3000]/docs"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,9 +1,9 @@
{
"github_tagfilter": "v",
"last_update": "2026-08-01",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "browserless_chrome",
"source": "github",
"upstream_repo": "browserless/chrome",
"upstream_version": "2.55.2"
"upstream_version": "2.55.3"
}

View File

@@ -1,4 +1,7 @@
## 9.13.0 (2026-08-08)
- Update to latest version from linuxserver/docker-calibre (changelog : https://github.com/linuxserver/docker-calibre/releases)
## 9.12.0 (2026-08-01)
- Update to latest version from linuxserver/docker-calibre (changelog : https://github.com/linuxserver/docker-calibre/releases)

View File

@@ -117,5 +117,5 @@ schema:
slug: calibre
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/calibre
version: "9.12.0"
version: "9.13.0"
video: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,9 +1,9 @@
{
"github_fulltag": "false",
"last_update": "2026-08-01",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "calibre",
"source": "github",
"upstream_repo": "linuxserver/docker-calibre",
"upstream_version": "9.12.0"
"upstream_version": "9.13.0"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,7 @@
## 07308543 (2026-08-08)
- Update to latest version from linuxserver/docker-baseimage-selkies (changelog : https://github.com/linuxserver/docker-baseimage-selkies/releases)
- Upstream tag : debiantrixie-version-07308543
## 2026.08.04 (04-08-2026)
- Fix: reverted the GPU acceleration added in 2026.08.03. It did not just fail to help — it was
what disabled the GPU. `--use-gl=angle --use-angle=gl-egl` forces Mesa's EGL X11 platform,

View File

@@ -136,5 +136,5 @@ schema:
slug: claude_desktop
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "2026.08.04"
version: "07308543"
video: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -4,6 +4,6 @@
"github_fulltag": true,
"slug": "claude_desktop",
"paused": false,
"upstream_version": "ubunturesolute-version-3a10bef7",
"last_update": "2026-08-01"
"upstream_version": "debiantrixie-version-07308543",
"last_update": "2026-08-08"
}

View File

@@ -1,4 +1,7 @@
## 2.10.3 (2026-08-08)
- Update to latest version from Cleanuparr/Cleanuparr (changelog : https://github.com/Cleanuparr/Cleanuparr/releases)
## 2.10.2 (2026-08-01)
- Update to latest version from Cleanuparr/Cleanuparr (changelog : https://github.com/Cleanuparr/Cleanuparr/releases)

View File

@@ -11,7 +11,7 @@
#=== Home Assistant Addon ===#
# ARGs used in FROM must be declared before any FROM instruction
ARG BUILD_UPSTREAM="2.10.2"
ARG BUILD_UPSTREAM="2.10.3"
#################
# 1 Build Image #

View File

@@ -91,5 +91,5 @@ schema:
TZ: str?
slug: cleanuparr
url: https://github.com/alexbelgium/hassio-addons/tree/master/cleanuparr
version: "2.10.2"
version: "2.10.3"
webui: "[PROTO:ssl]://[HOST]:[PORT:11011]"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,8 +1,8 @@
{
"last_update": "2026-08-01",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "cleanuparr",
"source": "github",
"upstream_repo": "Cleanuparr/Cleanuparr",
"upstream_version": "2.10.2"
"upstream_version": "2.10.3"
}

View File

@@ -1,4 +1,7 @@
## 19.20.0 (2026-08-08)
- Update to latest version from coderaiser/cloudcmd (changelog : https://github.com/coderaiser/cloudcmd/releases)
## 19.19.1 (2026-06-17)
- Update to latest version from coderaiser/cloudcmd (changelog : https://github.com/coderaiser/cloudcmd/releases)

View File

@@ -104,4 +104,4 @@ schema:
slug: cloudcommander
udev: true
url: https://github.com/alexbelgium/hassio-addons/tree/master/cloudcommander
version: "19.19.1"
version: "19.20.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,8 +1,8 @@
{
"last_update": "2026-06-17",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "cloudcommander",
"source": "github",
"upstream_repo": "coderaiser/cloudcmd",
"upstream_version": "19.19.1"
"upstream_version": "19.20.0"
}

View File

@@ -1,4 +1,7 @@
## 2.2.6 (2026-08-08)
- Update to latest version from ajslater/codex (changelog : https://github.com/ajslater/codex/releases)
## 2.2.5 (2026-08-02)
- Update to latest version from ajslater/codex (changelog : https://github.com/ajslater/codex/releases)

View File

@@ -101,4 +101,4 @@ schema:
slug: codex
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "2.2.5"
version: "2.2.6"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,9 +1,9 @@
{
"github_beta": "true",
"last_update": "2026-08-02",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "codex",
"source": "github",
"upstream_repo": "ajslater/codex",
"upstream_version": "2.2.5"
"upstream_version": "2.2.6"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,4 +1,7 @@
## 4.4.25 (2026-08-08)
- Update to latest version from ente/ente (changelog : https://github.com/ente/ente/releases)
- Fix build failure: upstream renamed the `ente-io` org to `ente`, so the base image is now `ghcr.io/ente/server` (GHCR does not follow the rename)
## 4.4.23 (2026-06-11)
- Update to latest version from ente-io/ente (changelog : https://github.com/ente-io/ente/releases)
## 1.7.24 (2026-06-05)

View File

@@ -30,7 +30,7 @@ RUN set -eux; \
# Pull the web source
WORKDIR /src
RUN git clone --depth 1 --branch "${ENTE_WEB_TAG}" https://github.com/ente-io/ente.git .
RUN git clone --depth 1 --branch "${ENTE_WEB_TAG}" https://github.com/ente/ente.git .
# Build web workspace (lives in ./web)
WORKDIR /src/web
@@ -52,7 +52,7 @@ RUN npm run build:memories
#################
# 1) Base image #
#################
FROM ghcr.io/ente-io/server:latest
FROM ghcr.io/ente/server:latest
##################
# 2) Tune image #

View File

@@ -31,7 +31,7 @@ _Thanks to everyone having starred my repo! To star it click on the image below,
---
[Ente](https://github.com/ente-io/ente) is a self-hosted, end-to-end encrypted photo and video storage solution. This addon provides a complete Ente server setup including the museum API server and MinIO S3-compatible storage backend.
[Ente](https://github.com/ente/ente) is a self-hosted, end-to-end encrypted photo and video storage solution. This addon provides a complete Ente server setup including the museum API server and MinIO S3-compatible storage backend.
Ente offers:
- End-to-end encrypted photo and video backup
@@ -41,7 +41,7 @@ Ente offers:
- Album sharing with family and friends
- Full control over your data with self-hosting
This addon is based on the official Ente server: https://github.com/ente-io/ente/tree/main/server
This addon is based on the official Ente server: https://github.com/ente/ente/tree/main/server
## Configuration

View File

@@ -1,6 +1,6 @@
{
"build_from": {
"aarch64": "ghcr.io/ente-io/server:927c6a316f181c7901446311f0085593b346b336",
"amd64": "ghcr.io/ente-io/server:927c6a316f181c7901446311f0085593b346b336"
"aarch64": "ghcr.io/ente/server:927c6a316f181c7901446311f0085593b346b336",
"amd64": "ghcr.io/ente/server:927c6a316f181c7901446311f0085593b346b336"
}
}

View File

@@ -131,6 +131,6 @@ schema:
slug: ente
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "4.4.23"
version: "4.4.25"
video: true
webui: http://[HOST]:[PORT:3000]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,9 +1,9 @@
{
"github_beta": "false",
"last_update": "2026-06-11",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "ente",
"source": "github",
"upstream_repo": "ente-io/ente",
"upstream_version": "4.4.23"
"upstream_repo": "ente/ente",
"upstream_version": "4.4.25"
}

View File

@@ -1,4 +1,8 @@
## 2026.08.04 (2026-08-04)
- Update to latest version from charlocharlie/epicgames-freegames
- Upstream tag : debian-2026-08-04
## 2026.08.01 (2026-08-01)
- Update to latest version from charlocharlie/epicgames-freegames
- Upstream tag : debian-2026-08-01

View File

@@ -88,5 +88,5 @@ schema:
slug: epicgamesfree
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "2026.08.01"
version: "2026.08.04"
webui: "[PROTO:ssl]://[HOST]:[PORT:3000]"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -2,10 +2,10 @@
"dockerhub_by_date": true,
"dockerhub_list_size": 2,
"github_exclude": "-",
"last_update": "2026-08-01",
"last_update": "2026-08-04",
"repository": "alexbelgium/hassio-addons",
"slug": "epicgamesfree",
"source": "dockerhub",
"upstream_repo": "charlocharlie/epicgames-freegames",
"upstream_version": "debian-2026-08-01"
"upstream_version": "debian-2026-08-04"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,4 +1,7 @@
## 1.5.1 (2026-08-08)
- Update to latest version from gtsteffaniak/filebrowser (changelog : https://github.com/gtsteffaniak/filebrowser/releases)
## 1.5.0 (2026-07-22)
- Update to latest version from gtsteffaniak/filebrowser (changelog : https://github.com/gtsteffaniak/filebrowser/releases)
## 1.4.0-2 (16-07-2026)

View File

@@ -114,4 +114,4 @@ schema:
slug: filebrowser_quantum
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "1.5.0"
version: "1.5.1"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,10 +1,10 @@
{
"github_beta": "false",
"last_update": "2026-07-22",
"last_update": "2026-08-08",
"paused": false,
"repository": "alexbelgium/hassio-addons",
"slug": "filebrowser_quantum",
"source": "github",
"upstream_repo": "gtsteffaniak/filebrowser",
"upstream_version": "1.5.0"
"upstream_version": "1.5.1"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,4 +1,7 @@
## 3.20.2 (2026-08-08)
- Update to latest version from wiserain/flexget
## 3.19.31 (2026-08-01)
- Update to latest version from wiserain/flexget

View File

@@ -95,5 +95,5 @@ schema:
slug: flexget
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "3.19.31"
version: "3.20.2"
webui: "[PROTO:ssl]://[HOST]:[PORT:5050]"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,9 +1,9 @@
{
"dockerhub_list_size": "10",
"last_update": "2026-08-01",
"last_update": "2026-08-08",
"repository": "alexbelgium/hassio-addons",
"slug": "flexget",
"source": "dockerhub",
"upstream_repo": "wiserain/flexget",
"upstream_version": "3.19.31"
"upstream_version": "3.20.2"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Some files were not shown because too many files have changed in this diff Show More