---
slug: moon-v2.3
title: moon v2.3 - Task tags, dep cache strategies, native file hashing, CAS cache, and more
authors: [milesj]
tags: [tasks, tags, targets, cache, dependencies, hashing, cas, mcp, performance]
image: ./img/moon/v2.3.png
---

This release focuses on giving you more control over how tasks are organized, referenced, and
cached. We're introducing first-class tags for tasks, a new cache strategy for task dependencies,
and a pair of experimental layers — native file hashing and a local CAS — that lay the groundwork
for further performance improvements.

<!--truncate-->

## Task tags

Projects have supported tags since v1, but tasks themselves have never had first-class support for
tags, until now. In v2.3, we're introducing [`tags`](/docs/config/project#tags-1) for tasks,
allowing you to label and organize tasks in a more flexible way. This opens up new possibilities for
filtering, grouping, and managing tasks based on their tags.

```yaml title="moon.yml"
tasks:
  lint:
    # ...
    tags: ['quality', 'ci']
```

To start, targets have been updated to support the `#` tag syntax in the task scope, allowing you to
do crazy things, such as:

- `:#tag` - Reference all tasks with the `tag` tag.
- `^:#tag` - Reference all tasks with the `tag` tag in upstream projects.
- `project:#tag` - Reference all tasks with the `tag` tag in a specific project.
- `#tag1:#tag2` - Reference all tasks with the `tag2` tag, in all projects with the `tag1` tag.

These targets can be utilized on the command line, enabling you to curate exactly which tasks to run
based on their tags.

```shell
$ moon run ':#quality'
```

Or they can be used in task dependencies, which will be expanded at runtime to a list of
fully-qualified targets.

```yaml title="moon.yml"
tasks:
  build:
    # ...
    deps: ['shared:#prereqs']
```

In addition to the new [`tags`](/docs/config/project#tags-1) setting, we've also added an
[`options.mergeTags`](/docs/config/project#mergetags) setting to control how tags are merged during
task inheritance, a [`taskTag`](/docs/concepts/query-lang#tasktag) field to MQL for querying tasks
by their tags, and a `--tags` filter for [`moon query tasks`](/docs/commands/query/tasks).

:::info

Heads up: the existing MQL `tag` field has been renamed to
[`projectTag`](/docs/concepts/query-lang#projecttag) to disambiguate from the new
[`taskTag`](/docs/concepts/query-lang#tasktag) field.

:::

## Task dependency cache strategies

When a task depends on another task, that dependency's changes need to invalidate the current task's
cache — but how aggressively? Until now, moon always mixed the dependency's hash into the current
task's hash. This meant that a `build` task depending on an upstream `build` would re-run any time
the upstream task's _inputs_ changed, even if the upstream task's _outputs_ ended up identical. Even
worse, depending on output-less tasks like `lint` or `test` invalidated builds for no real reason.

In v2.3, task dependencies now support a [`cacheStrategy`](/docs/config/project#cache-strategy)
field that controls exactly how a dependency contributes to cache invalidation:

- `hash` - Use the dependency task's hash for cache invalidation. The current task is invalidated
  whenever the dependency changes (inputs, command, args, env, etc.). This is the historical
  behavior.
- `ignored` - Ignore the dependency task's hash entirely. The current task is **never** invalidated
  by this dependency's changes — the dependency is purely a sequencing edge.
- `outputs` - Use the dependency task's _outputs_ instead of its hash. The current task is only
  invalidated when the dependency's output files change, not when its inputs change.

```yaml title="moon.yml" {4-6}
tasks:
  build:
    command: 'webpack'
    deps:
      - target: '^:build'
        cacheStrategy: 'outputs'
```

For build dependencies in particular, `outputs` is the strategy you almost certainly want. A change
to an upstream project's source files that doesn't affect its compiled output should not force a
downstream rebuild — and now it won't.

### Behavior change

To make this work for everyone out of the box, the default `cacheStrategy` is now chosen for you
based on whether the dependency declares outputs:

- A dependency **with** outputs (e.g. a `build` task) defaults to `hash`.
- A dependency **without** outputs (e.g. a `lint` or `test` task) defaults to `ignored`.

This is a behavior change from v2.2 and earlier, where the strategy was always effectively `hash`.
In practice, this means tasks depending on output-less tasks will see fewer (correct!) cache
invalidations going forward. If you want to restore the old behavior for a specific dependency, set
`cacheStrategy: 'hash'` explicitly.

## Experimental native file hashing

For as long as moon has existed, file hashing has been delegated to the VCS (typically Git). This
was a pragmatic choice — Git already has a hash for every file it tracks, so why hash them again?
But shelling out to Git for every hashing operation has overhead, and Git's hashing doesn't run on
moon's task pool, which means it can't take advantage of the parallelism we've spent the last few
releases building out.

In v2.3, we're introducing an experimental **native file hashing** implementation that runs directly
within moon's task pool, sidestepping the VCS entirely. In our benchmarks, this delivers a **10-50%
improvement** depending on workspace size and file count.

Enable it in your workspace configuration:

```yaml title=".moon/workspace.yml"
experiments:
  nativeFileHashing: true
```

The CAS hasher can also be tuned via the new top-level [`cache`](/docs/config/workspace#cache)
setting — see the workspace config docs for the available knobs.

## Experimental local CAS for task outputs

Speaking of caching — moon has shipped a remote content-addressable storage (CAS) backend since
v1.30 for remote caching, but locally, task outputs were still archived as tarballs. In v2.3, we're
introducing an experimental local CAS layer that stores task outputs in the same content-addressed
format used by the remote cache. This means deduplicated storage across tasks, and a unified cache
shape locally and remotely.

Enable it in your workspace configuration:

```yaml title=".moon/workspace.yml"
experiments:
  casOutputsCache: true
```

This is a foundational change — most of the work in this release went into reshaping the cache,
remote, and hashing internals around a shared CAS abstraction. Expect this to become the default in
a future release once it's been battle-tested.

:::warning

At this time, the local CAS may be slower than the tarball approach, as we have more guardrails to
prevent data loss and cache corruption. Additionally, archiving and hydration currently happen in
the main thread, and not through the daemon. In subsequent releases, we plan to optimize the local
CAS for performance and make it the default.

:::

## MCP tools for template discovery

For AI agents working in moon repositories, code generation is one of the highest-leverage
operations they can perform — but only if they know which templates exist and what variables those
templates accept. v2.3 adds two new tools to the [MCP server](/docs/guides/mcp) that close this gap:

- `get_templates` - Lists available templates with id, title, and description. Supports an optional
  case-insensitive filter regex (mirroring `moon templates --filter`).
- `get_template` - Returns the merged variable schema for a given template id, resolving the
  `extends` chain. Variables marked `internal: true` are omitted.

Together, these let an agent discover what's available, inspect a template's contract, and then call
`generate` with the right inputs — no more guessing at template ids or required variables.

> Thanks to [ateirney-nz](https://github.com/ateirney-nz) for this contribution!

## Unstable `uv pip` support for Python

In the Python ecosystem, `pip` is the default package manager, but tools like `uv` are gaining
traction for their speed and modern features. To better support Python developers, we've added
experimental support for `uv pip` as a package manager in the Python toolchain, alongside `pip` and
`uv`.

This new option can be enabled via the `unstable_python.packageManager` setting in your project
configuration:

```yaml title=".moon/toolchains.yml"
unstable_python:
  packageManager: 'uv-pip'
```

Because `uv pip` is a combination of `uv` and `pip`, that offers the speed and efficiency of `uv`
while maintaining compatibility with `pip`, the way it is configured is quite unique. For install
related args, the `unstable_pip` toolchain is used (because `pip` compatibility), while sync and
venv related args, the `unstable_uv` toolchain is used (because `uv` is used directly).

```yaml title=".moon/toolchains.yml"
unstable_python:
  packageManager: 'uv-pip'

unstable_pip:
  installArgs: ['--group', 'dev']

unstable_uv:
  syncArgs: ['--check']
  venvArgs: ['--clear']
```

## Other changes

View the [official release](https://github.com/moonrepo/moon/releases/tag/v2.3.0) for a full list of
changes.

- Added Deno v2.8 support to the JavaScript toolchain.
- Added Git SHA256 support for commit hashes. This is in preparation for Git's transition to SHA256
  as the default hash algorithm.
- Reduced task target memory footprint by 50-100%, further trimming the in-memory size of the
  project and task graphs.
- Fixed a glob regression where unbounded walks could be up to 10x slower.
