Files
glenn schrooyenandClaude Opus 5 9d593429d3
checks / unit (pull_request) Successful in 7m56s
SEC-2: write down the release path, now that master refuses a direct push
master is the default branch, which is what the Home Assistant Supervisor
clones - so whatever sits on it runs on a real 5 kW inverter. Until today it
had no branch protection at all, while every gate this repo built sat on
release/1.0, the branch that does not ship. 0.10.1 went straight onto master
on 2026-09-22 with no PR, no CI and no reviewer, and it succeeded.

master now carries the same rule as release/1.0 - enable_push false,
required context `checks / unit (pull_request)`, block_admin_merge_override,
required_approvals 0. That is a forge setting and cannot live in a commit, so
the part that can is this: the procedure it implies. Without it the next
release is attempted the old way and fails confusingly at the push.

docs/RELEASE.md records the path (merge to release/1.0, bump version, PR
release/1.0 -> master, wait for green, merge, then `ha store reload` before
the Supervisor offers anything), the before/after protection state of both
branches, and how the gate was proven to bite rather than merely badge green.

Documentation only - no code, no workflow change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-23 03:43:37 +02:00

5.1 KiB

Releasing — how a change reaches the battery

master is the default branch, and the default branch is what the Home Assistant Supervisor clones when it installs or updates this add-on. Whatever sits on master is what runs on a real 5 kW inverter. Treat a push to master as a deployment, because that is what it is.


1. master is protected — releases go through a pull request

Until 2026-09-23 master had no protection of any kind. Every gate this repo built (CI-01, CI-9) sat on release/1.0, the branch that does not ship. On 2026-09-22 version 0.10.1 went straight onto master — no pull request, no CI run, no reviewer — and it succeeded (commit 0f14c2a, the only commit on master that carries no (#NN) merge suffix). SEC-2 closed that.

The release path is now:

  1. Merge the work into release/1.0 as usual, via a pull request that goes green. Bump version: in goodwe_controller/config.yaml on that branch — the Supervisor keys the built image by version:, so without a bump nothing is offered and nothing changes.

  2. Open a pull request release/1.0 → master. Do not push to master; it will be refused by the pre-receive hook:

    remote: error: Not allowed to push to protected branch master
    ! [remote rejected] ... (pre-receive hook declined)
    
  3. Wait for checks / unit (pull_request) to go green on that pull request. The workflow in .gitea/workflows/checks.yml carries no branches: filter, so a master base matches and a real run happens — verified on throwaway PR #107 (base master), which queued a genuine run.

    ⚠️ Green means green. A skipped status is laundered into a success rollup by this Gitea and the merge gate accepts it — see the long note at the top of .gitea/workflows/checks.yml. Never add an event filter there.

  4. Merge the pull request. required_approvals is 0, so a green check is the whole gate; there is no second pair of eyes unless someone asks for one.

  5. Reload the store. Merging to master does not, by itself, offer the update. The Supervisor only sees the new version after:

    ssh root@192.168.2.158 "ha store reload && ha addons update local_goodwe_controller"
    

    Until ha store reload runs, the add-on page still shows the old version and the release looks like it did nothing.

Note that release/1.0 is normally an ancestor of master between releases, so a release/1.0 → master pull request is empty until new commits land on release/1.0. That is expected; it means there is nothing to release yet.


2. Branch protection — before and after SEC-2

Forge is Gitea at 192.168.2.147:3000, repo admin/goodwe-addon. Read it back with GET /api/v1/repos/admin/goodwe-addon/branch_protections.

Before (2026-09-23):

branch protected status check required context approvals
master (default) no rule at all — — —
release/1.0 yes yes checks / unit (pull_request) 0

After:

branch protected status check required context approvals
master (default) yes yes checks / unit (pull_request) 0
release/1.0 yes (unchanged) yes checks / unit (pull_request) 0

Both rules are otherwise identical: enable_push: false, enable_force_push: false, block_admin_merge_override: true, no whitelists, no file patterns. required_approvals stays at 0 on purpose — raising it would block the review agent from merging, and nobody asked for it.

The default branch was not changed. Changing it would change what the Supervisor clones, which is a different decision from this one.

How the gate was proven to actually bite

A protection rule on this forge can appear satisfied while having checked nothing, so the rule was tested rather than trusted. The discriminator is a merge POST carrying a deliberately wrong head_commit_id: Gitea evaluates branch protection before it checks the head, so

  • 409 head out of date → the request cleared protection;
  • 405 Not all required status checks successful → protection refused it.

Read the message, not just the code: 405 is also returned as Please try again later for a pull request with no commits, which has nothing to do with protection. An empty pull request on an unprotected base gives that same 405 (verified, throwaway PR #110), so an empty release/1.0 → master reports it too.

Validated against a control on an unprotected base (PR #106), which answered 409 while its check was still pending — i.e. protection was no obstacle there. On master (PR #107) the same probe answered 405 while pending, and 409 once the check was genuinely green. A direct git push to master was rejected by the pre-receive hook with master's SHA unmoved.


3. Rolling back

There is no force-push escape hatch: enable_force_push is false on both branches. To undo a bad release, land a revert on release/1.0, bump the version again, and take it through §1. The /addons/ rollback for the reference install is in HANDOVER.md.