v0.29.4

max-sixty/worktrunkv0.29.4Sep 23, 2026by github-actions[bot]

Upgrading?

Key Highlights

  • Destination branch expansion in pre-switch hooks now uses `{{ branch }}` for the target branch.
  • LLM tool commands are now included in example config via `wt config create`.
  • Fixed a blank line inconsistency in `config create` output.
  • Improved documentation for switch hooks and list placeholders.

New Features

  • Destination branch variable expansion in hooks
  • Commented LLM command examples in default config

Full Release Notes

**Minimum supported Tailscale client version: v1.80.0**

### Changes

- Fix a node being listed among its own peers in an incremental map update, which crashes the Tailscale Android app on the device list [#3459](https://github.com/juanfont/headscale/pull/3459)
- Fix deleting a node leaving its long poll open, so the client stayed connected instead of asking for a new login [#3449](https://github.com/juanfont/headscale/pull/3449)
- Fix interactive OIDC login when the confirmation page is reloaded by an ad blocker, back navigation, or pull-to-refresh; the confirmation page now has its own URL, keeping single-use authorization codes out of reloads [#3448](https://github.com/juanfont/headscale/pull/3448)
- Harden the OIDC callback: state and nonce cookies take their Secure flag from `server_url` so they survive a TLS-terminating proxy, a callback state is single-use, and an invalid `oidc.issuer` or a missing `oidc.client_id`/`oidc.client_secret` now fails at startup [#3334](https://github.com/juanfont/headscale/pull/3334)
- Fix HTTP metrics only counting `OPTIONS` requests, so `http_requests_total` and `http_request_duration_seconds` now cover regular traffic [#3414](https://github.com/juanfont/headscale/pull/3414)
- Fix extra-records filewatcher hanging on shutdown after the watched file is deleted, and leaking the watcher when setup fails [#3437](https://github.com/juanfont/headscale/pull/3437)
- Lowercase DNS extra record names so mixed-case records resolve [#3366](https://github.com/juanfont/headscale/pull/3366)
- Fix `headscale users rename` sending the raw `--identifier` flag value instead of the matched user's identifier, so renaming by name works again [#3442](https://github.com/juanfont/headscale/pull/3442)
- Fix tailsql not shutting down with headscale, leaving the process hanging on graceful shutdown [#3400](https://github.com/juanfont/headscale/pull/3400)
- Fix tvOS setup instructions: install the VPN configuration before setting the coordination server URL [#3431](https://github.com/juanfont/headscale/pull/3431)
- Map requests that only bump LastSeen, endpoints or DERP region no longer resend the whole node to every peer, and health probes that change nothing no longer write. Adds `headscale_mapper_changes_dropped_total` and `headscale_ha_health_updates_total` [#3417](https://github.com/juanfont/headscale/issues/3417) [#3450](https://github.com/juanfont/headscale/pull/3450)
- The peer map is keyed by node ID and reused for writes that cannot change peer visibility, so a routine map request no longer rebuilds it. Adds `headscale_nodestore_snapshot_builds_total` [#3417](https://github.com/juanfont/headscale/issues/3417) [#3450](https://github.com/juanfont/headscale/pull/3450)
- Fix an expired node staying online forever, because expiring it updated the key deadline without ending its map session [#3472](https://github.com/juanfont/headscale/pull/3472)
- Fix ACME renewal stopping permanently after a `badNonce` reply, because the error logging middleware drained the response body the acme client needs to detect it [#3461](https://github.com/juanfont/headscale/pull/3461)
- Fix `#`-prefixed metadata fields being rejected outside `acls`, so policy editors can store metadata in grants, SSH rules and `nodeAttrs` [#3481](https://github.com/juanfont/headscale/pull/3481)
- Fix exit nodes not offered by recent macOS and iOS clients, which read the `suggest-exit-node` peer attribute rather than the advertised `0.0.0.0/0` routes [#3487](https://github.com/juanfont/headscale/pull/3487)
- Fix exit node not offered to viewers whose only matching rule is a `via` grant; peer visibility now comes from the peer map alone [#3409](https://github.com/juanfont/headscale/pull/3409)

## Upgrade

Please follow the steps outlined in the [upgrade guide](https://headscale.net/stable/setup/upgrade/) to update your existing Headscale installation.

## Changelog
* 3a2b133a418d008e73b627b404c2b2465d10d4da .github/workflows: regenerate the integration test matrix
* bdfa3401ff40af2ccd4ad8e1e96069640907460c AGENTS.md: drop stale line numbers
* 8106636c7f8d0cf8c9d4fb0beae94415069b768b CHANGELOG: add 0.29.4
* b9b2beb78167888d3859eaf2f8dba942f6aec257 Update AppleTV configuration steps
* 2a76860bdb9dcc575e694448c14a50588b2846e7 build: bump test image Go to 1.27.1
* 974becf658e09cef54f243b1407f5008ba77a270 change, mapper: distinguish deleted nodes
* b3b55cc472959113d457ca99aca06cd4cd0a401b cli: fix users rename when resolved by name
* 46a80ea8f915a8f27c870fe8a2c2ed8d97cf68b1 dns: cancel extra-records retry on shutdown and close watcher on setup error
* 33db8c6b2940b716fb2e7bc5c848f98e8ba06b51 fix(metrics): collect metrics for non-OPTIONS requests
* 032470a5d3ef04dd866f15a5f493b13e42a88e84 hscontrol: cancel tailsql on graceful shutdown
* ebe18cebffb528efc53247054aa42994ece40fe5 hscontrol: keep the ACME error body readable in acmeLogger
* c7d9d035006d3b44b05e8545022d4e62067a0319 hscontrol: replace tailscale line refs with doc links
* 1e528f7f52c208b666d8884a1f24046fa148c324 integration: cover deletion across client versions
* b9c7b4c44beec953f15c92a8feadcbe8964807ce integration: cover node deletion ending the long poll
* 7783e7f679e6de1b5e28c13807e3276681725d39 integration: cover node expiry and recovery for every client
* 625fa86e1ac5129903765ec0bd2fcf664022b73e integration: pin docker client to daemon API version
* cc41593d9d48d99d5249f5a7bc4889a333faf7ba integration: pin which Hostinfo changes reach peers
* fc894aa9da6c63f38a4b2bcea402c44f769110d2 linting issue fix
* 2b8dbea41278ce5e22d35400f55f30307f557b49 mapper: assert no map response lists the recipient as its own peer
* b8da1c2d6f2ccb71812d3b095d9b9b50495c28df mapper: drop empty changes before fan-out
* 397149e9be086424447eb731593f6c49d2696132 mapper: stop a deleted node's map session
* 9fbf7b9b6010f74c84598ac3dd973a1970b0a905 mapper: take peer visibility from the peer map only
* d3882440258b0dd4742b0209ed3fcfb722f2f565 noise: make deleted-node expiry clock independent
* 2b28f5756b3b5b293568b1e03f5babc21f230079 oidc: harden callback CSRF cookies, state reuse, and issuer config
* cbb9357d1cb4ef43df37c7884a5fe4e826efad7b oidc: harden reloadable confirmation flow
* 2da47a77d66de2e5bd52a17b7be7fab5601578ea oidc: serve the registration confirmation page from a reloadable URL
* 938c2bd75303cd3f4b2c8cd3462c3a72b8e6cb8e policy,state: key the peer map by node ID
* bc5b9d56b26178dee7ce9660b76fa44f9eef792c policy,types: skip recompile when the user list is unchanged
* 8490631f06226a719f4106689be9e43e96a16706 policy/v2: add via exit node capture
* 694bafee0a254a87e4dac629efc64d5d7b999bab policy/v2: compare peer CapMap against SaaS route captures
* 5aded15bf2e53f21892decc702370346dd39876e policy/v2: suggest approved exit nodes by default
* aa4c952bca9c8d55e79a930d474099b5674e4845 policy: ignore '#' metadata fields across the whole policy
* fb197db16386ede870dad8d0cfb738940334fb5c poll, noise: tell a deleted node to re-authenticate
* 83eff4000a97faae552b908fd08280e26e918a97 poll: interrupt blocked map writes
* a7f7e6a4016b3c1e8877e60a0eae2277811105b2 servertest: compare user-owned nodes in via compat tests
* 8973347d228fbf92efc86af05c4cc2be95fc7b54 state: classify map requests before broadcasting
* d4b073e830f5782ebc3d637338e07e767bdc9598 state: exclude self from peers on the named peer-ID path
* a91f708adc70c4fa615e8b10b82c58d086599ba5 state: mark expired nodes offline without ending the session
* 0fca2bb646bb89736443344468c7fa4e8d56d941 state: preserve committed node deletion changes
* fdb782daa5974b78868ae156cc81a5da9fce7870 state: rename persist helpers to say what they do
* 6a0f67d56e2d031ac4f0a2cc23b644c2563a00ab state: resolve changed peers through adjacency
* 237dc74e73c5aca4716da9b11f0b462eb5ec007f state: reuse peer adjacency for payload-only writes
* 8f7ee375e9a608614e43925017f7164b57015587 state: skip node health writes that change nothing
* b972da3fe98e17338ad6dd01f64f10fb30a3c624 state: stop broadcasting a whole peer on disconnect
* e3dba1fdf9a8e88bfdd7a58fc2d318c2cd7e2d3b templates: apply the tvOS setup reorder to the served /apple page
* 2b6cb3a2cceb08bed82d533c060d033e5540e330 types/change: drop unused VisibilityChange
* 4411264ef992d69735e41469f51a098e778636f4 types: detect policy change on user identity and exit routes
* b4f991b3814144702371213b01152cb91b22a5b1 types: lowercase DNS extra record names
* e6c0a8adeeee30665c130e5c5c5468e5f4a82d6c types: regenerate node view

Related Repositories