diff --git a/ansible/roles/borgmatic/defaults/main.yml b/ansible/roles/borgmatic/defaults/main.yml
index 4dd0671..25d0149 100644
--- a/ansible/roles/borgmatic/defaults/main.yml
+++ b/ansible/roles/borgmatic/defaults/main.yml
@@ -6,6 +6,16 @@ borgmatic_log_dir: /Users/erichblume/Library/Logs
# Full path to borg binary since LaunchAgent doesn't have homebrew in PATH
borgmatic_local_path: /opt/homebrew/bin/borg
+# Borgmatic version — keep in sync with mise.toml in the repo root.
+# Ansible installs this via `mise install` so indri doesn't need the repo cloned.
+borgmatic_version: "2.1.4"
+
+# Full path to borgmatic binary — called directly by LaunchAgents to avoid
+# routing through mise, which triggers macOS TCC permission dialogs for
+# protected folders (e.g. ~/Documents) that hang headless LaunchAgent sessions.
+# Uses mise's "latest" symlink so version bumps don't break the LaunchAgent path.
+borgmatic_bin: /Users/erichblume/.local/share/mise/installs/pipx-borgmatic/latest/bin/borgmatic
+
# Schedule: runs daily at 2:00 AM
borgmatic_schedule_hour: 2
borgmatic_schedule_minute: 0
diff --git a/ansible/roles/borgmatic/tasks/main.yml b/ansible/roles/borgmatic/tasks/main.yml
index dd6efdd..eacefa5 100644
--- a/ansible/roles/borgmatic/tasks/main.yml
+++ b/ansible/roles/borgmatic/tasks/main.yml
@@ -1,6 +1,11 @@
---
-# Note: borgmatic is installed via mise (pipx), not managed here.
-# This role manages the config file and scheduled LaunchAgent.
+# Borgmatic is installed via mise (pipx) and called directly by LaunchAgents.
+# This role manages installation, config, and the scheduled LaunchAgents.
+
+- name: Install borgmatic via mise
+ ansible.builtin.command: mise install pipx:borgmatic@{{ borgmatic_version }}
+ register: borgmatic_install
+ changed_when: "'installed' in borgmatic_install.stderr"
- name: Ensure borgmatic config directory exists
ansible.builtin.file:
diff --git a/ansible/roles/borgmatic/templates/borgmatic-photos.plist.j2 b/ansible/roles/borgmatic/templates/borgmatic-photos.plist.j2
index 6e69159..d5b5578 100644
--- a/ansible/roles/borgmatic/templates/borgmatic-photos.plist.j2
+++ b/ansible/roles/borgmatic/templates/borgmatic-photos.plist.j2
@@ -14,10 +14,7 @@
ProgramArguments
- /opt/homebrew/opt/mise/bin/mise
- x
- --
- borgmatic
+ {{ borgmatic_bin }}
--config
{{ borgmatic_photos_config }}
create
diff --git a/ansible/roles/borgmatic/templates/borgmatic.plist.j2 b/ansible/roles/borgmatic/templates/borgmatic.plist.j2
index c7da8e8..a6422fe 100644
--- a/ansible/roles/borgmatic/templates/borgmatic.plist.j2
+++ b/ansible/roles/borgmatic/templates/borgmatic.plist.j2
@@ -14,10 +14,7 @@
ProgramArguments
- /opt/homebrew/opt/mise/bin/mise
- x
- --
- borgmatic
+ {{ borgmatic_bin }}
--config
{{ borgmatic_config }}
create
diff --git a/docs/changelog.d/+borgmatic-launchagent-tcc.bugfix.md b/docs/changelog.d/+borgmatic-launchagent-tcc.bugfix.md
new file mode 100644
index 0000000..0f941e9
--- /dev/null
+++ b/docs/changelog.d/+borgmatic-launchagent-tcc.bugfix.md
@@ -0,0 +1 @@
+Fix borgmatic LaunchAgent failing silently due to macOS TCC permission dialogs. LaunchAgents now call borgmatic directly instead of routing through `mise x`, which triggered "wants to access Documents" dialogs that hung headless sessions. The ansible role now also manages borgmatic installation via `mise install`.
diff --git a/mise.toml b/mise.toml
index ee3200e..12c92df 100644
--- a/mise.toml
+++ b/mise.toml
@@ -5,6 +5,7 @@
# 2. create a new entry in service-versions.yaml
# This will help ensure reviewed upgrades at a steady cadence
"pipx:ansible-core" = { version = "2.20.1", uvx = "true", uvx_args = "--with botocore --with boto3" }
+"pipx:borgmatic" = "2.1.4"
prek = "0.3.4"
pulumi = "3.215.0"
dagger = "0.20.1"
diff --git a/service-versions.yaml b/service-versions.yaml
index f6f3be4..277216d 100644
--- a/service-versions.yaml
+++ b/service-versions.yaml
@@ -352,10 +352,10 @@ services:
- name: borgmatic
type: ansible
- last-reviewed: 2026-03-16
- current-version: "2.1.3"
+ last-reviewed: 2026-04-15
+ current-version: "2.1.4"
upstream-source: https://github.com/borgmatic-collective/borgmatic/releases
- notes: Installed via mise (pipx), not managed by Ansible role
+ notes: Installed via mise (pipx); version pinned in ansible/roles/borgmatic/defaults/main.yml and mise.toml
- name: jellyfin
type: ansible