Files
werkator/docs/prs/2026-10-03-PR#103-max-concurrent-per-repository.md
miandmhoennig 018db2e425
werkator build succeeded after 01:47
werkator/deploy build succeeded after 00:06
werkator/mutation build succeeded after 40:05
executor.maxConcurrentPerRepository caps one repository's builds (v1.34.0) (#103)
For vm4006: one hsadmin-ng backend build next to a frontend build, never two backend builds.

- New instance-level key `executor.maxConcurrentPerRepository` (unset = only the global cap).
- A build takes its repository slot before a global one, so a waiting build never blocks another repository.
- Sized once like `maxConcurrent`; a change needs a restart.

PR-doc: `docs/prs/2026-10-03-PR#103-max-concurrent-per-repository.md`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #103
2026-10-03 21:01:01 +02:00

3.1 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

vm4006 builds hsadmin-ng (backend) and hsadmin-ng.frontend. The machine can take one backend build next to a frontend build, but not two backend builds. Werkator knew two limits: executor.maxConcurrent over all repositories, and one build per repository and branch. With maxConcurrent: 2, two backend branches could build at the same time; with maxConcurrent: 1, the frontend waited behind every backend build.

Non-Goals

  • No limit per registry entry: one value for all repositories covers the case; an exception per entry can follow when someone needs it.
  • No build-definition key: what a machine can take is the host's decision, so ADR 0010 does not apply.

The Scenarios

Feature: A cap per repository below the global one

Scenario#103.01: A second build of the same repository waits, another repository's runs

So that a heavy repository never builds twice at once, and a light one is not held up by it.

  • Given maxConcurrent: 2 and maxConcurrentPerRepository: 1
  • And a running build of one repository
  • When a second build of that repository and then a build of another repository are queued
  • Then the other repository's build takes the second slot, although it was queued last
  • And the second build of the first repository starts once its repository's slot is free
Verified by

Scenario#103.02: Unset by default, read like the rest of the section

So that every existing instance keeps its behaviour.

  • Given a configuration without and with executor.maxConcurrentPerRepository
  • When it is loaded
  • Then it is unset, or the value given
Verified by

The Solution

  • ExecutorConfig.maxConcurrentPerRepository, instance-level like the whole executor section, so no branch can raise it.
  • BuildExecutor sizes it once with the global semaphore and keeps one semaphore per RepoContext.
  • A build takes its repository's slot first and the global one second: a build waiting for its repository never sits on a global slot that another repository's build could use. A build cancelled while it waits for its repository's slot gives it back without waiting for a global one.
  • Like maxConcurrent, a change takes effect with the next restart.

Additional Changes

  • docs/configuration.md, docs/deployment.md, docs/llms.md, the init template, AGENTS.md and the architecture skill describe the key.
  • Version 1.34.0 with its release note.