Changelog

What changed in every release. The release notes explain the why.

1.7.0Release notes →

A detailed summary about the release and the reasons behind the changes can be found in the release blog. For the recommended setup, see agents, workspaces and CI.

Upgrade notes

  • One full rebuild after upgrading. Cache keys are now computed from project-relative paths (see below) and include the CPU architecture and the definitions of a task's dependencies, so every existing cache entry misses once.
  • More files count as sources. Globs now match what they say (see Fixed), so a task may see source changes it previously missed, and run.
  • A src that matches no file makes a task always run, like a task without src, and hammerkit warns about each such entry.
  • A task needs src of its own to be cached. A task without src whose dependencies declare src (a test task depending on build) used to be cached on its dependencies' sources alone, so changing only its own files (the tests) left it cached. It now always runs; declare the files it reads.
  • Tasks without src always run. A task with no src — and every task depending on one — runs on every invocation, as documented. Earlier versions cached such a task after its first run.
  • Dependencies of cached tasks are skipped. A task pulled into a run only as a dependency no longer runs when every task depending on it is a cache hit, so its outputs (e.g. an exported dist) aren't produced. Request the task explicitly or pass --no-skip-deps to run the whole graph as before.
  • Explicit cache pull/push fail on backend errors. The s3 backend now distinguishes a missing entry from a transport or credential error. Builds are unaffected: they still treat a backend error as a cache miss with a warning.

Added

  • registry cache backend: store cache entries in any OCI registry (GHCR, Docker Hub, ECR, GAR, Artifactory, registry:2) using your existing docker login credentials, including credential helpers. See caches.
  • hammerkit cache pull / cache push --remote <name>: move the current commit's cache entries between the local cache and a remote cache without running tasks — warm a workspace before building, publish results after. See cache pull / push.
  • Read-only cache mode: --cache-read-only on run/up, or HAMMERKIT_CACHE_READ_ONLY=1, restores from cache backends but never writes to them; cache push refuses to run.
  • Cache retention: a retention block on caches (maxAge, maxSize, keepPerTask), hammerkit cache ls and hammerkit cache prune (with --dry-run). Local caches with a policy are pruned automatically after every successful run; remote caches only with --remote. See retention.
  • Skipping dependencies of cached tasks, with --no-skip-deps to opt out. See dependencies of cached tasks.
  • Task timeouts: timeout on a task and --timeout as a default on run/up. A timed-out task fails, is cleaned up and writes no cache entry. See timeouts.
  • hammerkit explain [task] (with --json) reports whether each task is a cache hit or miss and which input caused a miss; run --explain prints the cause inline. See explain.
  • run --dry-run prints the execution plan with predicted cache hits and misses.
  • End-of-run build summary with the cache hit ratio (--no-summary, --summary-json).
  • hammerkit graph [task] prints the build graph as mermaid or dot. See graph.
  • A guide for coding agents that rewrites an existing CI pipeline into a hammerkit build file. See migrate CI with an agent.
  • The caching docs list what the cache can't see and how to check a build. See caching limitations.

Changed

  • The npm package ships only the CLI (dist) and build.schema.json: about half the size of 1.6.0, without test files or source maps.
  • Cache keys no longer depend on where the project is checked out. Paths in a task's or service's cache identity are relative to the project root (the git root, or the main build file's directory), so a laptop, a CI runner and an agent sandbox share cache entries. Machine-local runtime state (Docker containers, staging directory) stays scoped per checkout, so worktrees on one machine never share running containers. See ADR-0006.
  • The built-in default cache is now actually shared between checkouts on the same machine, as documented.
  • A service is only started when a task that needs it actually runs; cache hits don't start their services.

Fixed

  • Cache hits after a change — several ways a task was reported as cached although an input it reads had changed:
    • Globs only looked at the top directory: src/**/*.ts ignored every file in a subdirectory, and **/*.ts matched nothing at all.
    • Globs using ?, [...], {a,b} or extglobs, a * inside a file name (src/app*.ts) and globs with environment variables ($DIR/*.ts) matched nothing.
    • Binary sources were compared as text, so two different images or fonts could look identical.
    • Changing a dependency's cmds, envs or image rebuilt the dependency but not the tasks depending on it; with skipped dependencies, not even the dependency.
    • Deleting a task's output (rm -rf dist) left the task cached without the output.
    • A src entry with a typo kept its task cached forever.
  • A cache hit of a container task now restores its exported directories and its file outputs on the host. Previously only its volumes were restored, and file outputs were not stored at all.
  • A task whose src includes what a dependency generates now has the same cache key before and after the dependency ran, so a clean checkout (CI) hits entries pushed from a built workspace (an agent).
  • Cache keys no longer depend on the order in which the filesystem lists files, which differs between machines.
  • Cache entries (description.json, pushed to remote caches) and the local explain records no longer contain env values in plain text — only a digest.
  • Tasks running on Kubernetes now wait for their job to finish and fail when it fails. Previously a task was reported as completed as soon as its job was created. Cancelling a run (or a timeout) now deletes the running job.
  • Restoring a container task from a cache backend or store/restore now writes the outputs into the task's volumes. Previously they were written into a throwaway container and lost, so the task reported a cache hit with empty outputs.
  • A task that needs a service no longer fails the run when it is a cache hit.
  • A needed service that fails to start (missing image, port already in use) now fails the run immediately instead of hanging forever; service errors and crashes count as run failures.
  • cache push uploads outputs that are current in the checkout even when they were never stored in the local cache.
  • Stopping a local task — on a timeout or Ctrl-C — now stops every process its commands started. Previously only the shell was stopped: on Linux (where sh is dash) and for compound commands everywhere, the command kept running and the task waited for it to finish on its own.
  • A local task printing more than 1 MB of output is no longer killed.
  • A Kubernetes task keeps the job of its last run, so an unchanged task is reused in place instead of being restored from the cache backend on every run.
  • Running several commands in one process (getCli, tests) no longer leaks log output of one run into the next command's output.
  • A container service's image substitutes environment variables (image: $POSTGRES_IMAGE) like a task's image does.
  • build.schema.json is regenerated and matches the build-file schema again (continuous, service ports, registry backend).

A detailed summary about the release and the reasons behind the changes can be found in the release blog.

Added

  • Pluggable cache backends with automatic pull/push. A new top-level caches: block declares a cache with a method and a backend (local or s3). Tasks reference a cache through the cache field.
  • Run tasks and services on a Kubernetes cluster through a new environments: block, selected with the --env argument. Tasks run as jobs, container services as deployments.
  • Store and restore now work against a Kubernetes environment via --env.
  • Service healthchecks are translated into Kubernetes readiness and liveness probes.
  • Exportable generated files via generates: [{ path, export: true }], copying outputs back into the workspace.
  • Services can declare deps and needs, support parallel usage and named references.
  • New up and down commands to start and stop services directly.
  • Local tasks receive connection details of needed services as environment variables (HAMMERKIT_<NAME>_HOST, HAMMERKIT_<NAME>_PORT, HAMMERKIT_<NAME>_PORT_<containerPort>).
  • Optional namespace for Kubernetes services.

Changed

  • The default cache method is now checksum in all environments (previously modify-date locally, checksum only in CI). This pairs with remote backends — only content checksums are stable across machines — and removes the local/CI cache mismatch. Behavior change: set cache: modify-date (or --cache modify-date) explicitly to keep the old local behavior.
  • Introduced a runtime abstraction so the same build file runs on the local docker daemon or on Kubernetes.
  • Kubernetes port-forwarding no longer requires the kubectl binary; ports are forwarded through the kubernetes client.
  • Internal rename of the "node" concept to "task".

Fixed

  • Build graphs that mix deps and needs in a cycle are now detected instead of deadlocking.
  • Local tasks use a pid file to prevent concurrent runs of the same task; stale pids are swept automatically.
  • A crashing docker service is reported as a crash instead of terminating silently.
  • Improved error handling when a configured cluster is missing.
  • Various stability fixes for environment file replacement and Windows test runs.
1.5.0Release notes →

A detailed summary about the release and the reasons behind the changes can be found in the release blog.

Added

  • Services: long-running dependencies a task can need. Hammerkit starts them, waits for an optional healthcheck, and stops them when no longer needed.
  • Container services, run in the same network as container tasks with automatic DNS by service name.
  • Kubernetes services, forwarding pods/services/deployments from a cluster into a task.
  • Labels on tasks and build files, with --filter/-f and --exclude/-e to scope a command to a group of tasks/services.
  • New ls command to list all tasks and services.

Changed

  • CLI start-up is faster: build files (including references/includes) are now parsed lazily, only when a task executes or ls runs, instead of on every invocation.
1.4.0Release notes →

A detailed summary about the release and reason behind the made changes can be found in the release blog.

Added

  • Added a missing license file to the repo (MIT)
  • Added a best-practices folder to the repo with examples how to use hammerkit
  • Added a port list for docker tasks to map container ports to the local machine
  • Added a Linux and macOS test runner to the CI

Changed

  • 3 new logging modes (interactive, live, grouped). Interactive is the default mode, except in CI environments, which use live.
  • The watch flag gets removed from the task definition in favor of the new --watch argument. This allows hammerkit to watch the entire dependency chain and restart dependent tasks.
  • Move the local cache dir .hammerkit into the user directory. Removing the need to exclude it from the source code or builds.
  • Docker tasks use volumes instead of mounts for generated files. Increasing stability and performance hugely. Reducing conflict with local development environment and the --no-container flag.
  • The source code got updated to node version 16, since it will be soon the next LTS. The upgrade was needed to take advantage of the new AbortController. Hammerkit requires at least node version 15.
  • Replaced all usage of the defer class with the AbortController, which caused issues with unhandled promises on newer node versions.
  • Store and restore format has changed for docker tasks, because of the changes to generated files and the usage of volumes.

Fixed

  • Docker tasks on windows sometimes got stuck, because the docker daemon did not close the stream. The status of the container gets now regularly polled, to handle this exception.