HACS validation¶
Validates a HACS-distributed Home Assistant custom integration with the two official actions, so a consuming repository inherits the same gate HACS and Home Assistant Core apply.
hacs/actionruns the same code HACS uses to validate a repository (hacs.jsonexistence, brands, topics, …);categorydefaults tointegrationhome-assistant/actions/hassfestvalidates the integration manifest and structure, including custom integrations
Usage¶
on:
push:
pull_request:
jobs:
hacs-validate:
uses: nolte/gh-plumbing/.github/workflows/reusable-hacs-validate.yaml@<tag>
Custom repository vs. default store
Leave ignore empty for a default-store-grade run. HACS forbids ignored checks for default-store inclusion. A pure custom repository may ignore checks it can't satisfy, for example with: { ignore: "brands" }.
Release ZIP asset¶
For HACS integrations, reusable-release-publish.yml builds a <domain>.zip asset from custom_components/<domain>/ at the release's target commit and attaches it to the draft before flipping draft=false, so HACS never sees a release without its download. This happens automatically whenever the repository carries a custom_components/<domain>/manifest.json; no extra wiring is needed.
The full release-and-distribution contract is specified in spec/ha/hacs-release in claude-home-assistant.
Central configuration¶
name: Reusable — HACS Validate
# Validates a HACS-distributed Home Assistant custom integration with the two
# official actions, so a consuming integration inherits the same gate HACS and
# Home Assistant Core apply. Spec: claude-home-assistant spec/ha/hacs-release.
#
# - hacs/action — runs the same code HACS uses to validate a repository
# (hacs.json existence, brands, topics, …). category is
# required; this reusable defaults it to "integration".
# - hassfest — Home Assistant Core's own manifest/structure validator,
# able to validate standalone/custom integrations.
#
# Consumers call this on push and pull_request:
#
# jobs:
# validate:
# uses: ./.github/workflows/reusable-hacs-validate.yaml
on:
workflow_call:
inputs:
category:
description: |
HACS repository category passed to hacs/action. One of: integration,
plugin, theme, python_script, appdaemon, template. This reusable
targets HACS integrations; the default is "integration".
required: false
type: string
default: integration
ignore:
description: |
Space-separated list of hacs/action checks to skip. Valid names:
archived brands description hacsjson images information issues topics.
Leave empty for a default-store-grade run — per
spec/ha/hacs-release a default-store inclusion MUST NOT ignore any
check. A pure custom repository MAY ignore checks it cannot satisfy.
required: false
type: string
default: ""
run_hassfest:
description: |
Run hassfest. Keep true for integrations (the default). Set false for
non-integration HACS categories (plugin/theme/…) where hassfest does
not apply.
required: false
type: boolean
default: true
permissions:
contents: read
jobs:
hacs:
name: HACS Action
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
# A literal reference, not `hacs/action@${{ inputs.hacs_action_ref }}`.
# GitHub Actions does not allow an expression in a step's `uses:`, so
# that form made this whole file fail validation -- it never parsed and
# never ran, from 2026-06-26 until issue #398. The `hacs_action_ref`
# input existed only to feed the expression and is gone with it; there
# were no working callers to keep compatible.
#
# main's head rather than the 22.5.0 tag: that tag dates from 2022 and
# sits 15 commits behind, so pinning to it would trade mutability for a
# four-year rollback. Same call as hassfest below.
- name: HACS validation
uses: hacs/action@1ebf01c408f29afcb6406bd431bc98fd8cbb15aa # main
env:
# hacs/action calls the GitHub API for the issues/topics/brands checks.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
category: ${{ inputs.category }}
ignore: ${{ inputs.ignore }}
hassfest:
name: Hassfest
if: ${{ inputs.run_hassfest }}
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
# The Home Assistant docs reference hassfest as @master, but a branch is
# mutable, so it is pinned here instead. home-assistant/actions carries no
# usable release tag: 1.0.0 dates from 2020 and sits 158 commits behind
# master, so the pin tracks master's head. Pinned 2026-08-01 from a master
# head dated 2026-07-30. The comment names the branch only, so a Renovate
# digest bump can't leave a date behind -- and because a branch name is not
# a tag, the github-actions manager's github-tags lookup finds nothing here;
# renovate-configs/common.json carries a git-refs customManager that reads
# this exact line so the pin still ages.
- name: Hassfest validation
uses: home-assistant/actions/hassfest@ab22029681aa532bfe7de5774a9972d67bfbd2c0 # master