Keeping My Home Assistant Configuration Under Version Control (with AI Commit Messages)
How I set up Git inside Home Assistant to track changes, generate commit messages with AI, and maintain a clear history of my configuration
At some point I realized that my Home Assistant setup had crossed a line.
Not in size — but in importance.
I had:
- Automations that took weeks to tune
- YAML that I didn’t want to touch because “it works”
- Changes I made late at night and forgot about the next day
Backups were there, sure — but backups answer only one question:
“Can I go back to some previous state?”
What I actually wanted was:
- To know what changed
- To know when
- And ideally why
So I decided to put my entire Home Assistant /config directory under Git — and then automate the whole thing so I wouldn’t have to think about it ever again.
This is a log of what I ended up building, the decisions I made along the way, and the trade-offs I accepted.
Why git, and why inside Home Assistant
I didn’t want an external script, a cron job on another machine, or a CI pipeline somewhere else.
My constraints were:
- Everything runs inside Home Assistant
- No manual steps once set up
- No interactive authentication
- Safe by default (no secrets in Git)
Git is perfect for this:
- It’s already available in the HA OS environment
- It’s reliable and boring
- It gives diffs, history, and rollback
The only tricky part is orchestration — and Home Assistant automations turned out to be more than capable.
Where I did everything: VS Code App
I did all setup work using the VS Code App (formerly the VS Code add-on).
That choice alone removed a lot of friction:
- I could edit YAML and test commands in the same place
- The integrated terminal runs directly inside Home Assistant
- No SSH-ing around or guessing paths
From here on, every command I mention was run from the VS Code App terminal.
Initializing the repository
The first concrete step was simply turning /config into a Git repository:
cd /config
git init
Then I created a private repository on my Git provider and added it as a remote:
git remote add origin git@github.com:your-user/home-assistant-config.git
I deliberately chose SSH, not HTTPS:
- No tokens to rotate
- No credentials stored in plaintext
- Better suited for automation
SSH authentication
Since Home Assistant automations can’t answer prompts, SSH had to be:
- Passwordless
- Non-interactive
- Fully predictable
I generated a dedicated SSH key just for Home Assistant:
mkdir -p /config/.ssh
ssh-keygen -t ed25519 -f /config/.ssh/id_ed25519 -C "home-assistant"
No passphrase. This key exists only for Git access, nothing else.
I added the public key to my Git provider and kept the private key strictly inside /config/.ssh.
The known_hosts gotcha
The first push failed with:
Host key verification failed.
Which makes sense: SSH wanted confirmation.
Because this is an automated system, I solved it explicitly by populating known_hosts myself:
ssh-keyscan github.com >> /config/.ssh/known_hosts
That single command removed all interactivity.
⚠️ Security note This is safe for well-known providers like GitHub or GitLab. I would not do this blindly for an unknown SSH server.
What I absolutely did not want to commit
Before committing anything, I stopped and wrote a proper .gitignore.
This is not optional.
My Home Assistant config contains:
- Runtime state
- Cached data
- Local secrets
- Private keys
None of that belongs in Git.
Here’s the .gitignore I ended up with:
/.cloud
/.storage
/everything-presence-zone-configurator/fw_cache
/image
/tts
/.ha_run.lock
/.HA_VERSION
/callgrind.out.*
/home-assistant*
/profile.*.cprof
/zigbee.*
/.ssh
secrets.yaml
__pycache__
*.pyc
/custom_components
About secrets
I already followed HA best practices:
- All sensitive values live in
secrets.yaml - YAML files reference them via
!secret
That made excluding secrets.yaml easy and safe.
Once a secret hits Git history, it’s effectively leaked — even if you delete it later. I treated this as a hard rule.
Wiring git into Home Assistant
Home Assistant’s shell_command integration is simple but powerful enough for this.
I defined a small set of Git commands:
shell_commands:
git_add: >
sh -c "git -C /config add ."
git_has_staged_changes: >
sh -c "git -C /config diff --staged --quiet || echo yes"
git_diff_staged: >
sh -c "git -C /config --no-pager diff --staged -U35"
git_commit: >
sh -c "git -C /config commit -m '{{ states('input_text.configuration_commit_message') }}' --author 'Home Assistant <homeassistant@home.sibe.st>'"
git_push: >
sh -c "GIT_SSH_COMMAND='ssh -i /config/.ssh/id_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/config/.ssh/known_hosts' git -C /config push"
A couple of intentional decisions here:
- I check explicitly whether there are staged changes → no empty commits
- I scope SSH options only to the push command
- I set the commit author to something recognizable
- I never let Git prompt for anything
The AI part: generating commit messages
This was the fun part.
Instead of writing commit messages myself, I let Home Assistant’s AI Task integration summarize the staged diff.
The flow is:
- Get the staged diff
- Send it to the AI
- Ask for a short, clean commit message
- Store it in an
input_text - Use that value in
git commit
This makes commit history:
- Readable
- Consistent
- Surprisingly accurate
And if I don’t like the message?
I can still edit the input_text before running the automation manually.
The automation itself
At the end, everything is glued together by a single automation.
It runs:
- Once per night (for passive safety)
- Or manually via a button (when I want control)
It:
- Stages changes
- Exits early if nothing changed
- Generates a commit message
- Commits
- Pushes
- Logs success or failure
alias: Auto-sync configuration to git
description: ""
triggers:
- trigger: time
at: "03:17:39"
- trigger: state
entity_id:
- input_button.create_a_commit_and_push
from: null
to: null
conditions: []
actions:
- action: shell_command.git_add
metadata: {}
data: {}
- action: shell_command.git_has_staged_changes
response_variable: staged_check
- condition: template
value_template: "{{ staged_check.stdout == 'yes' }}"
- action: shell_command.git_diff_staged
metadata: {}
data: {}
response_variable: diff_result
- action: ai_task.generate_data
metadata: {}
data:
task_name: Generate git commit message
instructions: |-
Write a concise, clear git commit message for the diff below.
```diff
{{ diff_result.stdout }}
```
entity_id: ai_task.openai_ai_task
structure:
commit_message:
description: >-
The commit message: a short summary of all the changes being
committed. Sentence case without any special characters like quotes.
selector:
text:
multiline: false
type: text
multiple: false
response_variable: llm_result
- action: input_text.set_value
metadata: {}
target:
entity_id: input_text.configuration_commit_message
data:
value: "{{ llm_result.data.commit_message }}"
- stop: Just testing, not actually committing.
enabled: false
- action: shell_command.git_commit
metadata: {}
data: {}
enabled: true
response_variable: commit_result
- if:
- condition: template
value_template: "{{ commit_result.returncode == 0 }}"
then:
- data:
name: Git Commit
message: |
Commit succeeded: {{ commit_result.stdout.split('\n')[0] }}
action: logbook.log
else:
- data:
name: Git Commit
message: "Commit failed: {{ commit_result.stderr }}"
action: logbook.log
- stop: Commit failed
error: true
- action: input_text.set_value
metadata: {}
target:
entity_id: input_text.configuration_commit_message
data:
value: ""
enabled: true
- action: shell_command.git_push
metadata: {}
data: {}
enabled: true
response_variable: push_result
- if:
- condition: template
value_template: "{{ push_result.returncode == 0 }}"
then:
- action: logbook.log
data:
name: Git Push
message: "Push succeeded: {{ push_result.stdout }}"
else:
- action: logbook.log
data:
name: Git Push
message: >-
Push failed (code {{ push_result.returncode }}): {{
push_result.stderr or push_result.stdout }}
- stop: Git push failed
error: true
mode: single
What I like most is that it fails loudly:
- Any Git error stops execution
- Errors are logged
- Nothing half-succeeds silently
Why I’m happy with this setup
This gave me:
- A full audit log of my Home Assistant evolution
- The ability to answer “what changed?” in seconds
- Confidence to experiment more freely
- A safety net that’s incremental, not binary
Backups are still there — but Git is now my first line of defense.
And because everything runs inside Home Assistant itself, I don’t have to maintain or remember anything else.
Final thought
This wasn’t about “using Git because developers do”.
It was about:
- Treating my Home Assistant setup as something worth maintaining properly
- Reducing fear around change
- Making the system more legible to my future self
And honestly?
Once you have this, not having version history feels reckless.