# How the TeslaMate Updater Module Checks for New Releases

> Discover how the TeslaMate Updater module checks for new releases by polling the GitHub API every 72 hours. Stay informed about the latest stable versions.

- Repository: [TeslaMate/teslamate](https://github.com/teslamate-org/teslamate)
- Tags: internals
- Published: 2026-06-23

---

**The `TeslaMate.Updater` GenServer polls the GitHub API every 72 hours to compare the latest release tag against the running instance's version, storing an update notification when a newer stable version is detected.**

The Updater module in the `teslamate-org/teslamate` repository provides automatic update detection by querying the GitHub Releases API and comparing semantic versions. Implemented as a supervised GenServer in [`lib/teslamate/updater.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/updater.ex), this component ensures users are notified when new stable releases become available without manual intervention. Understanding its polling mechanism and version comparison logic helps operators configure appropriate check intervals and troubleshoot update detection issues.

## Architecture and Scheduling

The Updater operates as a background GenServer that manages its own timer-based scheduling. When initialized, it sets up two distinct timers to control when checks occur.

### Initial Delay and Interval Configuration

During `init/1`, the server reads the `:check_after` and `:interval` options from the application environment, defaulting to 5 minutes and 72 hours respectively. The implementation uses `:timer.send_interval/2` to schedule recurring `:check_for_updates` messages every interval period. If `:check_after` is configured with a non-zero value, `Process.send_after/3` schedules a one-off initial check after the specified delay, allowing the application to stabilize before the first network request.

```elixir

# Default configuration in config/config.exs

config :teslamate, TeslaMate.Updater,
  check_after: :timer.minutes(5),
  interval: :timer.hours(72)

```

## The Update Check Flow

When the GenServer receives a `:check_for_updates` message (either from the interval timer or the initial delay), it delegates the work through a continuation pattern to avoid blocking the process mailbox.

### Triggering via handle_continue/2

The `handle_info/2` callback receives the `:check_for_updates` message and immediately returns a `{:continue, :check_for_updates}` tuple. This triggers `handle_continue/2`, which logs the start of the check and invokes the private `fetch_release/0` function to perform the actual HTTP request.

### Fetching from the GitHub API

The `fetch_release/0` function constructs a GET request to `https://api.github.com/repos/teslamate-org/teslamate/releases/latest` using the Tesla HTTP client. The request includes JSON middleware for automatic response decoding, targeting the latest stable release endpoint rather than listing all releases.

```elixir

# Inside lib/teslamate/updater.ex (simplified)

defp fetch_release do
  client = Tesla.client([{Tesla.Middleware.BaseUrl, "https://api.github.com"}])
  Tesla.get(client, "/repos/teslamate-org/teslamate/releases/latest")
end

```

### Parsing the Release Response

Upon receiving a successful 200 response, `parse_release/1` extracts three critical fields from the JSON payload: `tag_name`, `prerelease`, and `draft`. The `tag_name` (e.g., `"v1.30.0"`) is stripped of its leading "v" character and parsed into a `Version` struct using `Version.parse!/1`. The function returns a `%Release{}` struct containing the normalized version and boolean flags indicating whether the release is a prerelease or draft.

```elixir
defp parse_release(%{"tag_name" => "v" <> version, "prerelease" => pre, "draft" => draft}) do
  %Release{
    version: Version.parse!(version),
    prerelease: pre,
    draft: draft
  }
end

```

## Version Comparison Logic

Back in `handle_continue/2`, the Updater applies business rules to determine whether the fetched release constitutes a valid update for the running instance.

### Stable Release Filtering

The module only considers non-prerelease versions as valid updates. If `release.prerelease` is `true`, the version is logged and discarded, ensuring beta releases do not trigger update notifications in production environments. Draft releases are similarly handled according to the parsed `draft` flag.

### Semantic Version Comparison

For valid stable releases, the Updater compares the fetched version against the current application version (stored in the `@version` module attribute sourced from [`mix.exs`](https://github.com/teslamate-org/teslamate/blob/main/mix.exs)) using `Version.compare/2`. If the comparison returns `:lt` (meaning the current version is less than the fetched version), the new version is stored in the GenServer state as `%State{update: version}` and an informational log message is emitted.

```elixir

# Version comparison logic

case Version.compare(@version, release.version) do
  :lt -> 
    new_state = %State{update: release.version}
    Logger.info("Update available: #{release.version}")
    {:noreply, new_state}
  _ ->
    {:noreply, state}
end

```

## Querying Update State

Other application components can query the Updater's state without triggering network requests using the public API function `get_update/0`. This performs a synchronous GenServer call and returns the version string (e.g., `"1.30.0"`) if an update is available, or `nil` if the running version is current.

```elixir
case TeslaMate.Updater.get_update() do
  nil -> 
    IO.puts("Running the latest TeslaMate version")
  version -> 
    IO.puts("Update #{version} available")
end

```

## Practical Examples

### Manual Trigger and Check

For testing or operational scripts, you can manually trigger a check or query the current state:

```elixir

# Start the GenServer (normally supervised)

{:ok, _pid} = TeslaMate.Updater.start_link(check_after: 0, interval: :timer.hours(72))

# Manually trigger an immediate check

:ok = GenServer.cast(TeslaMate.Updater, :check_for_updates)

# Check after a brief moment

Process.sleep(100)
TeslaMate.Updater.get_update()

```

### Testing with Mocked GitHub Responses

The test suite demonstrates how to verify update detection without hitting the live API:

```elixir
defmodule TeslaMate.UpdaterTest do
  use ExUnit.Case, async: true
  import Mimic

  test "detects newer stable release" do
    TeslaMate.HTTP.Mock
    |> expect(:get, fn "/repos/teslamate-org/teslamate/releases/latest" ->
      {:ok, %Tesla.Env{
        status: 200, 
        body: %{
          "tag_name" => "v9.9.9",
          "prerelease" => false,
          "draft" => false
        }
      }}
    end)

    {:ok, _pid} = TeslaMate.Updater.start_link(check_after: 0, interval: :timer.hours(1))
    Process.sleep(100)

    assert TeslaMate.Updater.get_update() == "9.9.9"
  end
end

```

## Summary

- **[`lib/teslamate/updater.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/updater.ex)** contains the GenServer implementation that schedules and executes update checks using Elixir's `Version` comparison logic.
- The module polls the GitHub API endpoint `/repos/teslamate-org/teslamate/releases/latest` every 72 hours by default, configurable via `:interval`.
- **Prereleases and drafts are explicitly filtered**; only stable releases trigger update notifications.
- Version strings are normalized by stripping the "v" prefix and parsed into `Version` structs for reliable semantic comparison.
- **`get_update/0`** provides a safe, synchronous query mechanism for the web interface and other consumers to check for available updates without network side effects.

## Frequently Asked Questions

### Why does TeslaMate wait 72 hours between checks?

The default 72-hour interval balances staying current with new releases against unnecessary API rate limit consumption and network overhead. According to the source configuration, operators can adjust this via the `:interval` application environment setting in [`config/config.exs`](https://github.com/teslamate-org/teslamate/blob/main/config/config.exs) to check more or less frequently based on their operational requirements.

### How does the Updater handle GitHub API failures?

The `fetch_release/0` function returns `{:error, reason}` tuples on HTTP failures or non-200 status codes. In `handle_continue/2`, these errors are logged but do not crash the GenServer; the state remains unchanged and the next scheduled check attempts the query again after the configured interval expires.

### Will the Updater notify me about beta or prerelease versions?

No. The `parse_release/1` function specifically examines the `prerelease` boolean field in the GitHub API response. As implemented in [`lib/teslamate/updater.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/updater.ex), releases marked as prereleases are ignored entirely and will not populate the update state, ensuring only stable releases trigger notifications.

### Can I disable automatic update checks entirely?

While there is no explicit "disabled" flag in the module, setting the `:interval` option to an extremely large value (e.g., `:timer.hours(999999)`) effectively disables periodic checks. Note that a `:check_after` value of `0` will still trigger one initial check at startup unless the interval is also configured to prevent subsequent messages.