Release Notes
what changed in each versionv1.37.12 — 2026-10-09
- Internal: the reading of the legacy
bwrapsection and the check ofrestartsInstanceare split into their steps; that an explicitwerkdocksection wins and that the old name is warned about once per file is tested.
v1.37.11 — 2026-10-09
- Internal: whether a repository is private is answered from the cache and renewed from the forge in separate steps; the moments a cached answer expires and the single warning about a silent forge are tested.
v1.37.10 — 2026-10-09
- Internal: the build rows, the summary of the Repositories page and the attributes every page carries are built in smaller steps; the artifact page builds its row from the API's own shape, like every other page. Three of the five methods above the Mutation-CRAP target of 15 are below it now.
v1.37.9 — 2026-10-09
- Internal: the rules of the AI review by werkrevisor live in
.werkrevisor.tomlof this repository, including the risk categories for Java and Kotlin backends.
v1.37.8 — 2026-10-06
- Internal: agents open pull requests as WIP and mark them ready for the AI review by werkrevisor, which gates the merge together with the build. No change in behaviour.
v1.37.7 — 2026-10-05
- Internal: the status line of the Repositories page is split by status. With it no method is above a Mutation-CRAP of 15 any more, and the nightly mutation build now holds every method to that target. No change in behaviour.
v1.37.6 — 2026-10-05
- Internal: pruning the artifact store is split into its steps and takes the write lock directly, with tests for its journal line. No change in behaviour.
v1.37.5 — 2026-10-05
- Internal: the checks of the managed nginx settings and the files of
init --systemdare split into their steps, with tests for every refused setting and for whatinit --systemdprints. No change in behaviour.
v1.37.4 — 2026-10-05
- Internal: the RAM and disk sizing advice is split into its steps, like the CPU advice in v1.34.10, with tests for the column a peak is read from and for every advice chip. No change in behaviour.
v1.37.3 — 2026-10-05
- Internal: the choice of git credentials and the reading of open pull requests are split into smaller steps, with tests for which remote commands get credentials. No change in behaviour.
v1.37.2 — 2026-10-05
- Fix: two builds that need the same new werkdock image no longer import it at the same time. Since
v1.37.0 a CI build and a review of one repository start together; on mih09 one of them failed in
werkdock import, the other ran on a half-imported image. Now one build of the instance imports, across all repositories, and the others wait for it and use the image. - A build that fails before its commands give a result — the worktree, the image, a runtime error
with exit code 125 — reports
build environment failed after …to Gitea instead ofbuild failed, and says so in its log: there is no verdict of the build, and a restart may help.
v1.37.1 — 2026-10-05
- Internal: the configuration's checks of triggers and pull-request target builds are split into smaller steps, with tests for every warning they give. No change in behaviour.
v1.37.0 — 2026-10-05
- Pull-request target builds have a pool of their own: they take no slot of
executor.maxConcurrentormaxConcurrentPerRepository, and a review no longer waits for the CI build of its repository. The new instance keysexecutor.maxConcurrentPullRequestTargets(default 2) andexecutor.maxConcurrentPullRequestTargetsPerRepository(default 1) limit them; each pool has its own queue. - A pull-request target build no longer waits for the builds of its head branch, since it runs in a fresh workspace. Two runs of the same pull request still run one after the other.
- The Current Builds view marks such a build as
PR target; the System page counts it in aPR targetstile of its own instead of in the builds tile. export:forgejogives apull_request_targetworkflow a concurrency group per pull request, and notes what it cannot carry over.
v1.36.1 — 2026-10-05
- Security fix for pull-request target builds: they no longer share anything the repository's other
builds can write. In the werkdock sandbox such a build gets a fresh, empty home as
/rootinstead of.git/werkator/buildenv/home, where any branch's build could leave a.bashrcorsitecustomize.pythat ran with the secrets, and nowerkdock.gradleHome; in Docker it gets neither the Gradle cache volume nor the Docker socket. - A pull-request target build without
dockerorwerkdock, or withdocker.socket: true(also inherited frombuilds.default), is refused at start: on the host it would share the host user's HOME.
v1.36.0 — 2026-10-04
- Pull-request target builds: a build with
trigger.onPullRequestTarget: trueruns once for every new head of every open pull request that is not a draft, like Forgejo'spull_request_target. It checks out the base branch, never the pull request's files, and publishes its status on the pull request's head. The build learns the pull request fromWERKATOR_PR_NUMBER,WERKATOR_PR_HEAD_SHA,WERKATOR_PR_HEAD_REF,WERKATOR_PR_BASE_REF,WERKATOR_PR_BASE_SHAand the forge fromWERKATOR_GITEA_BASE_URL,WERKATOR_GITEA_OWNER,WERKATOR_GITEA_REPO. - Such a build is the host's alone: a branch's
.werkator.ymlcan neither add one nor change its command, environment or anything else, so its secrets and its verdict cannot be taken over by the change it judges. It needsgiteaandgit.token, and it stands alone: another trigger,requirePullRequest,builds.defaultor a follow-up of it are refused at start. export:forgejoconverts it to apull_request_targetworkflow on the base commit that skips drafts.
v1.35.2 — 2026-10-04
- No change for users. The health verdict collects its findings in one place and judges a disk as its budget and as the volume behind it; the System page is assembled from its sections; whether a disk budget is raised by asking is one rule for the verdict and the sizing advice. The nightly mutation gate falls from 32 to 31.
v1.35.1 — 2026-10-04
- No change for users. The Forgejo export finds what a workflow cannot carry over topic by topic
— container, werkdock sandbox, log files, host rules, steps —, writes a job as its header and
its steps, and
export:forgejoreads as its steps: find the repository, convert, report, write.
v1.35.0 — 2026-10-04
- A repository the forge hides from visitors stays hidden in Werkator too. Werkator now asks the forge
without its token what a visitor may see, so a repository that is
internalor sits in a private organisation is private here as well; before, only theprivateflag counted, and such a repository served its artifacts and logs to everyone. - A forge that does not answer hides a repository, even one that was public at the last answer; it is asked again after a minute.
- An artifact link naming an unknown or hidden repository answers 404, not a server error.
- A private repository can release chosen artifacts to everyone:
builds.<name>.publicArtifactslists globs over the workspace paths, written likeartifactDirs, or saystrue. The list is read from the built commit's own.werkator.yml, and only a branch the checkout's configuration names literally in that build'strigger.branchespublishes —masterforrelease, never a branch matched by*. Pages, branches and every other file stay hidden. - A released artifact is readable by the scripts of the websites named in
server.corsAllowedOrigins, and their preflight requests are answered; an artifact that is not released carries no CORS header.
v1.34.10 — 2026-10-04
- No change for users. The CPU sizing advice asks its two questions separately — did the load queue for the threads, and if not, how busy were they — and its thresholds are tested at their boundaries. The nightly mutation gate falls from 34 to 32.
v1.34.9 — 2026-10-04
- No change for users. A build definition is laid over its branch's settings in three blocks: what the build runs, the rules it runs under, and its sandbox; the docker and werkdock overrides each apply themselves. The nightly mutation gate falls from 35 to 34.
v1.34.8 — 2026-10-04
- No change for users. One sample of the System page is now taken in named steps: the host's readings, the disks with the repository size, the snapshot, the history. The nightly mutation gate falls from 36 to 35.
v1.34.7 — 2026-10-04
- No change for users. Tests for removing stale nginx containers, for the processor-time display, and for the disks of the System page; a path in the instance file is read by one rule for repositories and disks alike. The nightly mutation gate falls from 46 to 36.
- The nightly mutation build no longer fails on an analysis error: two waits that a mutant could make endless now fail a test instead of filling the memory of PIT's minion.
v1.34.6 — 2026-10-04
- No change for users. A build command's process — its output to the logs, its processor time, its deadline, ending its process tree — and the three log files of a build are parts of their own, with a test against real, short processes that the nightly mutation build keeps.
v1.34.5 — 2026-10-04
- No change for users. The executor's decisions — command order, cancelling, shutdown, duplicates, the restart after a deployment — have a test that starts no process and so runs in the nightly mutation build; the queued and running builds are kept in a register of their own.
v1.34.4 — 2026-10-04
- No change for users. What Werkator asks the host — which Docker socket, which jar it runs from, the machine's name, where it keeps its state — is decided by rules with tests of their own; the state directory's rule, written out three times, is one. The nightly gate falls from 57 to 47.
v1.34.3 — 2026-10-04
- No change for users. A build runs in three steps of its own — admitted, run, settled — and the slots of the instance and of each repository are one part with tests of their own; the Mutation-CRAP of the build's run fell from 648 to 6, and the nightly gate from 650 to 57.
v1.34.2 — 2026-10-04
- No change for users. The repository cards of the home page and the entries of the Repositories page name their newest build through one part each, which decides once that a repository has not built yet; their Mutation-CRAP fell from 224 and 18 to below 6, and the counter over the cards from 66 to 3.
v1.34.1 — 2026-10-04
- No change for users. Werkator's own methods aim at a Mutation-CRAP of at most 15, red in the code city from 12; the nightly mutation build fails above a ratchet that starts just above today's highest score and goes down with every refactoring. werkcrappit 0.16.4.
v1.34.0 — 2026-10-03
executor.maxConcurrentPerRepositoryin the instance file caps the builds of one repository belowexecutor.maxConcurrent: with 2 and 1, a heavy repository and a light one build side by side, two builds of the heavy one never do. A build waits for its repository's slot before it takes a global one, so it never blocks another repository while it waits.
v1.33.0 — 2026-10-03
- Werkator measures swap traffic, the pages swapped in and out over the last five minutes, and shows it
on the swap tile. From 0.1 MiB/s the tile is amber, from 1 MiB/s red; the instance file sets
both with
metrics.swapTrafficWarningMibPerSecondandmetrics.swapTrafficCriticalMibPerSecond. Swap that merely stays never turns it red: on Hostsharing only changed blocks go through the DRBD mirror.
v1.32.0 — 2026-10-03
- The tiles of the System page and the home page take the verdict's colours: load above the threads amber and at twice the threads red, CPU pressure from 20 % amber, RAM pressure from 2 % amber and from 10 % red, swap at half its size amber as well. Disk pressure stays uncoloured until its numbers are understood.
- The builds tile says how many slots there are,
1 of 2 running, and is amber with one slot free and red with none.
v1.31.0 — 2026-10-03
- Swap in use above
metrics.swapWarningGibin the instance file, 0.5 GiB by default, shows amber on the System page and on the home page. On Hostsharing swapped pages still go into the backup and the DRBD mirror. Only a display: the health verdict and the sizing ignore swap as before.
v1.30.5 — 2026-10-03
- No change for users. The decisions of the build executor — the final status, the timeout, the processor time, the overhead warning, the restart after a deployment, the stored result — moved into small classes with fast unit tests, so that the nightly mutation testing covers them.
v1.30.4 — 2026-10-03
- No change for users. The cases of
BuildExecutorIntegrationTestthat depended on the host's load wait for gates instead of fixed sleeps, and the build too short to measure no longer depends on one clock tick of a shell's start.
v1.30.3 — 2026-10-03
- A queued build that is cancelled says so at once; until now it stayed pending until the build ahead of it finished, hours behind a mutation build, and a restart in between ran it after all.
v1.30.1 — 2026-10-03
- The × of a queued or running build cancels it; until now it deleted the build's row while the build ran on out of sight, holding its branch — a deploy queued behind it waited for hours — and turning up again with its result once it finished. Deleting such a build is refused, and a build whose row is gone can still be cancelled.
v1.30.0 — 2026-10-03
server.corsAllowedOriginsin the instance file names websites whose scripts may read artifact files, so that a CodeCharta viewer elsewhere loads a code city straight from Werkator. Empty by default; no credentials are ever shared.
v1.29.1 — 2026-10-03
- No change for users. The nightly build
mutationno longer cleans the whole build directory, which deleted the runtime bundle a deploy queued behind it was to install; it runs at 00:00 UTC and withoutBuildExecutorIntegrationTest, which once aborted it.
v1.29.0 — 2026-10-03
werkator export:forgejo, wrapped bytools/werkator-to-forgejo, converts the committed.werkator.ymlinto Forgejo Actions workflows: one per build definition that triggers on its own, its follow-ups as jobs withneeds. What has no workflow counterpart becomes a comment and a line on stderr;--strictrefuses instead.- ADR 0010 keeps the build configuration expressible as a Forgejo Actions workflow, and RFC 0006 proposes one workflow file that runs on Forgejo and on Werkator, and the steps towards it.
v1.28.0 — 2026-10-03
- No change for users. Werkator measures its own tests: a nightly build
mutationruns PIT with werkcrappit onmainand keeps the report with the Mutation-CRAP of every method and a CodeCharta code city as artifacts.
v1.27.2 — 2026-10-01
- No change for users. The test specs are named after what they need,
*UnitTest,*RestTestor*IntegrationTest, and the build has the tasksunitTestandintegrationTest; the groundwork for mutation testing on the cheap half.
v1.27.1 — 2026-10-01
- A
.mdartifact is served astext/markdowninstead oftext/plain, so that a Markdown viewer in the browser renders it.
v1.27.0 — 2026-10-01
- The artifact page lists Markdown reports: a
.mdfile directly in a report directory, inreports/or one of its sub-directories, gets its own link next to the report's index page. werkcrappit'smutation-crap.mdbeside PIT'sindex.htmlwas archived but not listed. - A
.mdartifact is shown as UTF-8 text; it was offered as a download before.
v1.26.0 — 2026-10-01
- Only the repository the instance deploys itself from may restart it. With a registry, Werkator refuses
restartsInstancein every repository whose entry in~/.werkator.ymldoes not sayselfDeploys: true, and does not serve that repository. Before, a deployment block copied into a foreign repository ran that repository's build on the host and would have restarted the instance. init --applymay be given more than once; the fragments are merged in order.tools/remoteappliesWERKATOR_SELF_DEPLOY_CONFIGto the watched repository alone, andrepo-addrefuses a shared fragment that setsrestartsInstance.
v1.25.0 — 2026-10-01
- Werkator asks a rootless Docker daemon where it publishes ports. With the
slirp4netnsport driver that is the child address rootlesskit reports, usually10.0.2.100, and Werkator passes it to the build asTESTCONTAINERS_HOST_OVERRIDE; with thebuiltindriver it stayslocalhost. A daemon switched between the two needs no configuration change. An entry indocker.envstill wins.
v1.24.1 — 2026-10-01
- A
TESTCONTAINERS_HOST_OVERRIDEindocker.envis no longer overwritten by Werkator's own default. A rootless Docker daemon with theslirp4netnsport driver publishes ports on10.0.2.100, not onlocalhost; every Testcontainers test failed with "connection refused" until the host could say so.
v1.24.0 — 2026-09-30
- A build keeps its place in the History when retention removes its artifacts:
retentionPerBranchandretentionMaxAgenow decide over the artifacts only, and up to 100 builds per branch stay as a record with status, commit and duration. Their row says n/a for the artifacts. - The duration trend shows the last 30 successful builds of a definition instead of the last 30 days. A release or nightly build with only one branch gets a trend at last, and so does a repository that is rarely built.
- The History lists the newest 200 builds; the trend still reads every record.
v1.23.2 — 2026-09-29
- The nightly Docker cleanup keeps the build images Werkator built itself, except in the night to Sunday.
A nightly build an hour after the cleanup had to rebuild its image first; with a long test suite that
cost the four minutes to the build timeout. Once a week the images are still built fresh, with the
current base image and packages. An existing host gets the change by running
init --systemdagain.
v1.23.1 — 2026-09-27
- The pull request has a column of its own in Latest, Branches and History, and a line of its own on a phone. The copy button stands next to the branch name again, and a card on a phone no longer grows wider than the screen. Current Builds and the build page show it apart from the branch as well.
v1.23.0 — 2026-09-27
- A branch with an open pull request shows it as
PR#12beside its name, linked to the pull request in Gitea: in Latest, Branches, History and Current Builds, on the build page, on the Repositories page and on the home cards. It needs the Gitea connection and the token the statuses are published with; the watcher reads the open pull requests once per poll cycle. A private repository shows no pull request to a visitor without the control token.
v1.21.3 — 2026-09-19
- The nightly Docker cleanup also removes anonymous volumes no container uses any more. A Docker host
had collected 281 PostgreSQL data directories, 13 GB, because
docker system prunenever touches a volume. Named volumes, the Gradle caches, stay. An existing host gets the step by runninginit --systemdagain.
v1.21.2 — 2026-09-19
- A RAM downsize no longer waits for a peak below 40 %: it is advised as soon as a smaller bookable size holds the peak, its headroom and the host's reserve, and nothing ever waited for memory. A host peaking at 7 of 12 GiB is told that 10 GiB would do.
v1.21.1 — 2026-09-17
- A disk is sized by the level it holds, not by its highest quarter hour: one moment in which two caches lay side by side no longer keeps a quota on upgrade for a month. The level is what the last week sustained; the window maximum is still named and still vetoes a downsize.
- Disk growth is what most days show, the median of the daily deltas of the last week, and it is projected from the level. Three build environments imported on three days no longer read as a disk growing half a gigabyte a day.
v1.21.0 — 2026-09-17
- Build environments no repository names any more are removed again: the werkdock images Werkator
imported for a
werkdock.rootfsthat has since been replaced, and the download caches of URL sources. The watcher does this at most once an hour and never while a build executes; images pulled by anything else stay. Until now every imported rootfs stayed in the store for good, one to two GiB each.
v1.20.0 — 2026-09-17
- Sandboxed builds can share one Gradle user home:
werkdock.gradleHomenames a host directory that every sandbox naming the same path mounts as/gradle-user-home, withGRADLE_USER_HOMEpointing there. One dependency cache and one provisioned JDK for all repositories of a host instead of a copy per repository. Pinned and opt-in: the path is mounted read-write, and a shared cache is shared trust.
v1.19.1 — 2026-09-16
- The live indicator shows
restartingduring a declared self-deployment or while the proxy serves Werkator's maintenance page. It returns toliveafter recovery; an outage lasting more than two minutes is still shown as an error.
v1.19.0 — 2026-09-16
- Branches can define their own sandboxed follow-up builds with
trigger.afterSuccessOf, for example downloadable samples after successful tests. Explicit host triggers and follow-ups with host execution, Docker socket access or instance restart remain protected.
v1.18.3 — 2026-09-15
- A Docker build records no processor time instead of almost none. Its work runs in containers below the daemon, not below the process Werkator started, so sampling that tree measured the log relaying — and an eight-minute build claimed to have used no processor at all.
- An artifact is served under its own file name. Without one, Spring's guard against reflected
file downloads named every response
f.txt, so a build's jar was saved under that name; what a browser can display stays inline, everything else is offered as a download. - The generated systemd unit declares
SuccessExitStatus=143: a stop sends SIGTERM and the JVM exits 143 for it, which systemd was recording asFailed with result 'exit-code'— every clean stop looked like a crash. Existing instances get it wheninit --systemdwrites the unit again.
v1.18.2 — 2026-09-15
builds.<name>.docker.socketworks. The key was read and pinned correctly, but a build definition had no field for it, so applying the definition silently dropped it and the socket was never mounted — a build using Testcontainers found no Docker environment at all. Only the deprecatedbranchessection could set it.- A build definition can now say every docker and werkdock setting there is, and a test compares the two class pairs so the next added key cannot be forgotten again.
v1.18.1 — 2026-09-15
- The test that proves processor time is counted across a process tree no longer demands a whole second of it. On a loaded build host the measured loop gets a fraction of a core, which is a correct measurement and was failing the build.
v1.18.0 — 2026-09-15
- A build now records where its time went: the wait for a free slot, preparing the worktree,
preparing the build runtime, the clean and the build command, and storing the artifacts.
The artifact page shows the phases beside the duration, and
GET /api/builds/…carries them in milliseconds. - When a build spends more than
executor.overheadBudget(30s by default) outside its own commands, the journal says so and names the slowest of those phases. The queue wait does not count — waiting for a slot on a host that runs one build at a time is not overhead.
v1.17.1 — 2026-09-15
- The Load and Swap tiles on the System page show one decimal instead of two. They carry three and two numbers respectively, and at two decimals the line wrapped inside the tile. The second decimal of a load average decides nothing anyone reads off that page.
v1.17.0 — 2026-09-15
- The System and home pages now also show how much time was spent waiting for storage, beside the existing figures for processor and memory (PR#69). It completes the set: all three of the kernel’s pressure figures are now collected and kept in the history.
- Nothing is judged by it yet — no verdict and no sizing advice reads it. A number needs a stretch of history behind it before anyone can say what it means, and on a shared machine the disks are what is shared hardest, so the figure will say as much about the neighbours as about this instance until it has been watched for a while.
v1.16.1 — 2026-09-15
- The processor time of short builds is no longer reported far too low (PR#68). The figure was read once a second, and everything a build burned after its last reading was lost — which for a build lasting a second or two is most of it. It is now read five times a second, so the loss is a fifth of a second at worst.
v1.16.0 — 2026-09-15
- Every build now records how much processor time it actually used, beside how long it took (PR#67). The two are different questions: a build that took twice as long may have done twice the work or spent the time waiting, and only the processor figure tells them apart. The build page shows it next to the duration, with the factor against the wall clock — below one means the build waited more than it worked, above one means it used more than one core.
- This is the number that would have made the outage of two releases ago visible on its own: half an hour in which the instance could not start a single program looked like a slow half hour, and would have shown almost no processor time at all.
v1.15.1 — 2026-09-15
- Closed a gap in the handover introduced one version earlier (PR#66). An instance that had just deployed itself could still let one waiting build start in the instant before it stopped taking work — the very situation the previous release was meant to end. That build would have been cut short by the restart and repeated afterwards.
v1.15.0 — 2026-09-15
- An instance that deploys itself now ends itself instead of being stopped from outside (PR#65). A build definition can be marked as the one that installs the version the instance is to run next; when such a build succeeds, the instance stops taking new work, lets everything that is still building finish and be recorded, and only then hands over to the new version. Until now the restart was scheduled from outside and asked the instance whether it happened to be idle, so a build that started in the moment between the question and the stop was cut short and had to be repeated.
- While an instance is handing over, the pages say so: the banner explains that a new version was installed and that no build starts until the handover is done. That is the one situation in which the queue stands still and nothing is wrong.
- The setting is the host’s alone, like the sandbox and the build trigger, so no branch can give itself the power to end the instance. A configuration that would restart for every build, or for whatever branch happened to go green last, is refused at start rather than obeyed.
v1.14.3 — 2026-09-15
- A self-deployment no longer replaces the running instance’s own runtime underneath it (PR#64). It used to install the new version and restart half a minute later, and in between the running instance could not start a single program — not the version control it polls with, not a build command — because its runtime had been swapped out from under it. Every repository reported its origin as unreachable for that half minute, and the disk measurement of that moment was wrong for a month afterwards. The new version is now put in place while the service is down, in the moment between stopping and starting it.
- The restart also stopped waiting a fixed half minute for the deploying build to finish. It asks the instance whether anything is still building and acts as soon as the answer is no, which is usually within seconds. If something keeps building for ten minutes it restarts regardless, and the interrupted build is picked up again on the way back, as after any restart.
v1.14.2 — 2026-09-15
- A disk whose quota cannot be read is now left unmeasured instead of being measured against the whole volume (PR#63). The two numbers describe different things — this installation’s slice against the shared disk it sits on — and they can differ by a factor of twenty. When the quota command could not be run for a few seconds, the volume’s figure went into the disk’s own series as a peak, and because the sizing advice reads the peak of the last thirty days, a single unreadable sample recommended an upgrade for a month. Such a sample is now simply absent, which is what it always was.
- A machine that genuinely has no quotas is unaffected and keeps being measured against its volume. What changed is only that “there is no quota” and “the quota could not be asked for” are no longer the same answer.
v1.14.1 — 2026-09-15
- The self-deployment introduced in the previous release could not actually install anything, and said so instead of trying (PR#62). The runtime Werkator ships to a host is cut out of the Java development kit that compiled it, native libraries included, so that kit’s own build platform decides where the runtime will start. The kit inside the build sandbox is the one its Linux distribution ships, built against that distribution, and it produced a runtime the production host — an older distribution — could not start at all. The check that runs the new runtime before installing it caught this on the first real attempt: nothing was swapped, nothing restarted, and the running version stayed up.
- The build now asks for a vendor whose kit is built against a deliberately old base, so the runtime it produces starts on old and new systems alike. One line still decides the Java version, and it now decides it for the host as well.
v1.14.0 — 2026-09-13
- Werkator can now install its own merged commits (PR#61). Every other repository deploys with an ordinary follow-up build; this one could not, because its deploy build is a child of the very process it would replace — stopping the service ended the build, the build was recorded as interrupted, and the startup recovery ran it again, deploying and restarting in a loop. The build therefore restarts nothing: it installs the new runtime and hands the restart to a transient systemd unit that the stop cannot end, which fires once the build is safely recorded as successful.
- Installing a runtime is now staged wherever it happens. The new bundle is unpacked beside the running one and its launcher is started on the host first — which is what catches a runtime that will not run there at all — and only then does it take the place of the old one. Until that moment nothing has moved, so a broken bundle costs a red build and no downtime. What it replaces is kept as the last known good version, and the same transient unit puts it back when the restarted instance does not answer within two minutes.
- Deploying from a developer machine goes through that same staged swap now, so the two ways of installing a runtime cannot drift apart.
v1.13.1 — 2026-09-13
- Nothing changes in how Werkator runs. This release steadies one test of the build executor that could fail for something that was never a fault (PR#60). With a single build slot, two builds started back to back race for it, and the test assumed the one started first would win. It usually did — until a loaded CI machine let the second one win, run to the end and release the slot, so the test found a finished build where it expected a queued one. The test now enqueues the second build only once the first demonstrably holds the slot.
v1.13.0 — 2026-09-13
- The History page is a history again (PR#59). A build whose branch was deleted from origin — every merged pull request, that is — used to be erased along with the branch, so a busy day of merges left the page showing this morning’s three builds and nothing before them. The entry now survives its branch: status, commit, times and duration stay, within the same retention limits as any other build.
- What does go with the branch are its stored artifacts and its log — nobody is going to re-read the report of a branch that no longer exists, and the disk is the scarcer resource. Those rows say “n/a” where the artifact link used to be, and they no longer link to a Gitea branch page that would only answer 404.
- Builds are kept five per branch instead of three by default. Three was a single afternoon of merges.
v1.12.0 — 2026-09-12
- The sizing advice judges RAM the way it already judged CPU — by whether anything actually waited for it (PR#58). A fill level is not a shortage: a build filling the memory it was given had this machine in red at 95 % while nothing ever stalled on it. The peak is now only named, and it is the memory pressure the kernel measures that asks for a raise. Swap that has been held for days plays no part either — cold pages are not a shortage.
- Every recommended size is one that can actually be booked: a quota in its 10 GiB steps, physical storage in 250 GB, RAM in whole GiB. And a downsize is only offered when the next smaller size would still leave the headroom the advice itself asks for — “downsize to 11 GiB” named a size nobody can order, and the 10 GiB below it would have been flagged as tight on the next poll. Where there is nothing sensible to shrink to, the advice says so.
- The system metrics now also count OOM kills per sample, from
/proc/vmstat. Nothing reads them yet — they are the one memory shortage nobody waits for, and the history has to exist before it can be judged.
v1.11.2 — 2026-09-12
- A commit pushed while a build of the same branch was still running was skipped — and then never built (PR#57). The watcher read “this branch has new commits” from its local ref, but fast-forwarded that very ref at the end of the same cycle, so the later cycle it promised the skipped branch never came and the branch stayed behind origin until the next push or a restart. The watcher now remembers a branch it deferred, instead of inferring it from a ref.
v1.11.1 — 2026-09-12
- The System entry's nav icon was a pie chart, but the system page shows only bars and sparklines — no pie anywhere on it. It's now a small activity/pulse line, matching the sparklines actually on that page.
v1.11.0 — 2026-09-12
- The instance navigation carries an icon in front of every entry — Home, Repositories and System (PR#56) — and on a phone it no longer needs to scroll sideways just because its labels are too wide to fit. Home drops out there, since the wordmark and logo already lead home, and Repositories/System collapse to their icon alone.
v1.10.0 — 2026-09-11
- An HTML artifact is no longer part of this site (PR#55). A build writes its own reports, and
they were served on the instance’s own origin with scripting enabled — so a page a
build produced could read the control token an operator’s browser keeps and act under
their name. Artifacts now carry
Content-Security-Policy: sandbox allow-scriptsandX-Content-Type-Options: nosniff: the report opens and its scripts still run, but on an origin of its own, with no way back into the instance. - A Docker build no longer gets the daemon socket unless it asks for it (PR#55). The socket
was mounted into every build container, and the daemon acts outside the boundaries this
runner sets — a container started through it can bind-mount paths the build itself
cannot reach. The new
docker.socketis off by default and pinned likedocker.enabled, so a branch cannot hand it to itself. A build that needs a daemon — anything using Testcontainers — must now setdocker.socket: truein the repository's configuration, or it will not find one. - A werkdock build can no longer write the primary checkout (PR#55). The whole repository
directory was mounted read-write, so a build could rewrite the committed
.werkator.ymlat the repo root — the very layer the pinned settings are read from, which would have let it decide the next build's sandbox policy. The checkout is now read-only; writable are this branch's worktree and its git admin directory, which is what a build actually needs. - A build cannot have its artifacts read host files for it (PR#55). Collecting artifacts followed symlinks, and it runs on the host, outside the sandbox the build ran in — so a link was enough to copy a file the build could not open itself into the artifact store, which a public repository serves to anyone. A link leading out of the collected directory now aborts the collection: nothing of that build is stored, and the build is recorded as failed even when its commands succeeded. Links that stay inside are copied as before.
- A branch can no longer switch off its sandbox by writing nothing (PR#55). The pinned
settings were removed from the branch layer key by key, which only works where there is
a block to remove them from:
docker: nullleft nothing to strip, and the merge then replaced the host's whole Docker section, so the build ran natively on the host. Such a layer is now refused by name, and the pinned values are written back from the host's own configuration after merging — whatever shape the branch layer had.builds.<name>: nullkeeps its meaning: this branch runs no such build. - A build cannot name a host file as its log (PR#55).
stdoutLogandstderrLogwere resolved against the staging directory, and an absolute name comes back out of that unchanged — so a branch could have the server open any file its user may write, truncate it, and write the build's output into it. Both must now be plain file names; anything else fails the build, naming the key.
v1.9.0 — 2026-09-11
- The instance has a home page (PR#54, RFC 0003).
/now shows what is happening across every served repository and how the machine is doing, instead of one repository's latest builds: a system rail on the left — the verdict, one row per gauge of the system page and four smaller tiles, each a link into it — and a mosaic of repository cards on the right, one per repository, each with the bar strip of its last twelve builds, where the height is the duration and the colour the outcome. The wordmark leads there, and the navigation gained a Home entry. - The repository's Latest view moved from
/to/latest(PR#54). The unscoped routes still mean the served repository —/latest,/branchesand/historyall do — only/now belongs to the instance. The legacy/index.htmlkeeps leading to the latest builds, which is the page it always meant. - Light and dark are the reader's choice (PR#54). Dark has existed for every page since RFC 0001, but only as the browser's preference; a button in the header now overrules it and the choice is remembered in that browser. The browser stays the default.
- A private repository answers a visitor like a repository this instance does not serve
(PR#54, RFC 0003 D9). Redacting hid its contents but not its name: a guessed name answered
200 with redacted rows while an invented one answered 404, which confirmed exactly the
thing a repository named after a customer or an unreleased product most needs hidden. All
named routes —
/repos/<name>/…and/api/repos/<name>/…— now answer 404 without the control token. Its metadata are unchanged: that a build ran, when, how long and with what outcome stay visible, on the home page's bar strip too.
v1.8.1 — 2026-09-11
- Werkator's own Testcontainers smoke test stops skipping itself where it matters (PR#53). Its
build runs on a webspace in a werkdock sandbox, which had no engine to talk to, so the one
test that exists to prove Testcontainers works ran only on machines where nobody doubted it.
DOCKER_HOSTnow points at the werkdock API socket the runner already binds into every sandbox, and the test starts a real container there — which makes it the test that would notice if werkdock's Engine API subset stopped being enough. A host with no engine at all still skips, so a Docker-less build stays green.
v1.8.0 — 2026-09-11
- A credential left empty no longer disables the one it was meant to inherit (PR#51). The
machine config
initwrote carriedaccount: ""andtoken: "", and since that layer wins, those empty values overrode the instance-widedefaults.git— five of six repositories on the author's instance had quietly stopped reporting commit statuses to Gitea. A blank credential is now dropped instead of honoured, naming the file to fix, because there is no build that wants to authenticate as nobody. A blank setting still overrides, which is how "report nowhere" stays sayable. initwrites the credentials commented out (PR#51), with the detected account offered inside the comment. On an instance with shared credentials, leaving the block commented out is how a repository inherits them — a template that assertstoken: ""makes a claim it has no basis for. A configuration file holding nothing but comments is now a layer that sets nothing, where it used to be unreadable.- Build environments are imported with werkdock's
importverb (PR#50), thedocker importsemantics that replacedload -i --name. An older werkdock is detected and the previous form used instead, so an instance that has not been updated keeps building. - A stale werkdock binary is rebuilt before it is deployed (PR#50).
tools/remotecompares the binary against the sources beside it and runsgo buildwhen they are newer, then prints the version it is about to upload — twice in one day a deployment had silently shipped a months-old sandbox. - From this release on, every pull request carries its own version and release-notes entry (PR#52) instead of a deployment bundling whatever accumulated. PR#50 and PR#51 both called themselves v1.7.0, which is exactly what a version is supposed to prevent.
v1.7.0 — 2026-09-10
- The History page shows how long each build has been taking (PR#49). One card per build definition, over the last 30 days: the line of the window's successful runs, the median, the fastest and the slowest, and where the latest run sits. A build is called out in amber once it needs a quarter longer than its median — the point is to see a build getting slower while it happens, not after it has become a failure.
- The trend is grouped by build definition, not by branch (PR#49). A branch lives for days and leaves two or three measurements; the definition it runs outlives every branch. The median rather than the average keeps a single cold cache from colouring a whole month.
- New endpoint
/api/builds/durations(PR#49), repository-scoped like every other one. A private repository answers with no trend at all: how often a build runs and how big it is, is itself something a visitor without the token must not learn.
v1.6.0 — 2026-09-10
- A build that hangs no longer blocks the machine (PR#48). The host names the longest any build
may run in
executor.buildTimeout, 30 minutes by default, and a build stopped by it is recorded as timed out — its own status, because nothing about the work failed and nobody decided to stop it. The slot is free again immediately. - A build definition may ask for less time in
builds.<name>.timeout, never for more (PR#48): a longer value is capped to the host's, and since the wholeexecutorsection is out of a branch's reach it cannot raise the ceiling either. So a branch can promise to be quick without being able to occupy the machine.
v1.5.3 — 2026-09-10
- A control token that is not accepted says so (PR#47). Entering one now asks the server first and reports a wrong token while the clipboard is still open, instead of storing it and returning a page that silently looks unchanged. A stored token the server refuses turns the key in the header red and names itself as the reason.
- The instance logs where its control token lives when it starts (PR#47). With several served repositories that file is in the one the systemd unit runs from, not in any of the others, and a token taken from the wrong one is just a wrong token.
v1.5.2 — 2026-09-09
- A newly entered control token takes effect at once (PR#45). The pages and the JSON API answer
differently depending on the token cookie since v1.5.0, but were sent without any caching
header, so a browser could redisplay the answer it already had and the token looked as if it
had not worked. They are
no-storenow, and say that they vary by cookie — which also stops a reverse proxy from ever handing one visitor's page to another. Artifact files stay cacheable as before and only state the dependency. - The key in the header asks before it forgets the token (PR#45). It sits next to the reload button, and a misclick used to cost the token that then has to be fetched from the host again.
v1.5.1 — 2026-09-09
- Werkator's own repository configuration named the wrong forge (PR#44): its
gitea.baseUrlstill pointed at the GitHub mirror the repository was initialised from, not at the Gitea it is built by. Nothing in the product changes, but on the instance serving this repository the visibility question introduced in v1.5.0 could not be answered and the repository hid itself, and its commit statuses had been posted into the void all along. The repository's now redundantserversection is gone with it, which is one warning less on every start. - The order mail addresses the two parties separately (PR#44): CPU and RAM are hardware of the Managed Server and are bought, a disk quota is a service on the group and is granted, so each gets its own paragraph and the subject names what the mail is actually about. Every line says the size to end up with — auf insgesamt 6 GB erhöhen.
v1.5.0 — 2026-09-09
- A private repository no longer shows its work to everyone (PR#43). Without the control token its name reads hidden private repo in grey, branch, build name and commit read n/a, there are no links into the forge, and artifacts, logs and the artifact page answer as if nothing were there. What stays is that builds run, that some failed, and when. Whether a repository is private is not configured — the forge is asked and the answer cached; where no forge is configured nothing changes, and where a configured one stays silent the repository is hidden until it answers.
- The control token is kept in a cookie as well (PR#43), so a page knows what it may show before any script runs. The cookie grants visibility only: restarting or deleting a build still needs the token in the request header, which a foreign page cannot set. The order button follows the same rule and appears for the operator only.
- Restart, delete and cancel are greyed out without a valid control token (PR#43), instead of asking for one on the first click and failing if it is wrong. A key button in the header takes the token and gives it back.
- A quota that is nearly full is yellow now, not red (PR#43). It is raised by asking, and nothing stops while it is being asked for. Red is kept for what cannot be asked for: a volume that is the budget itself, or one filling up behind a quota, where raising the quota buys nothing. The machine verdict follows the same distinction.
v1.4.1 — 2026-09-09
- The sizing advice stops asking for hardware nobody needs (PR#42). CPU is sized from the load a quarter hour actually sustained instead of the highest minute inside it, so a compiler that briefly queues four tasks no longer reads as a demand for five threads. RAM is advised in the size it is bought in — whole GiB, including the slice the host keeps for itself — instead of half-GiB steps of visible memory. And a disk trend now needs a week of history and has to show in both halves of the window: one build environment unpacked once used to become a daily growth rate and, extrapolated over a month, an order for many times the quota. The advice also names the budget it means, so raising a group quota is not read as ordering hardware.
- On a Hostsharing Managed Webspace the advice can be ordered (PR#42). An order… button next to the sizing chips opens a dialog listing every recommended change as a checkbox — raising preselected, shrinking not — and writes the ticked ones into a German e-mail to the service desk, naming the webspace and the machine. Nothing is sent from the page: the mail client opens with the text prepared. Anywhere else — a Container Server, a plain machine — the button does not appear: the webspace is recognised by its home directory and its account name, and neither alone is taken as proof.
- A resource in the watch band gets its own verdict (PR#42): between 80 % and 90 % the chip says watch, raise to N in the same yellow the gauges use, instead of waiting silently until 90 % turns it red. A disk also keeps a third of its budget free rather than a fifth, because memory pressure passes and a full filesystem does not.
- A CPU is counted in threads now, not cores (PR#42) — that is what the operating system reports and what a webspace is billed by.
- The breakdown button of a disk card is a pale pie-chart icon in the row of the big number now (PR#42), where CPU and RAM show their average, instead of a text link below the note.
v1.4.0 — 2026-09-09
- The System page charts say what level they reached (PR#40): a line above watch is drawn yellow and one above critical red, cut exactly where it crosses — in the 1 h and 4 h views as well, which had no coloured line at all. The maxima line of the longer views follows the same rule instead of being red by decree.
- The CPU and RAM cards show the average of the selected period (PR#40), right-aligned next to the current value and marked with the average sign, so a spike can be told from a level.
- The sizing advice starts after one day instead of seven (PR#40); between one day and a week it is given but marked preliminary, reliable from 7 d.
- Every disk card can say what fills it (RFC 0002, PR#40): a dialog shows build workspaces, build environments, artifacts and the repositories themselves as a donut with sizes and shares, plus one other slice for everything that is not Werkator's. The numbers come from the walk that already produced the repository size, so nothing new is measured.
- A volume running out behind a quota that still has room is now visible (RFC 0002, PR#40) instead of silently moving every number: where a quota applies it stays the gauge's scale, and the volume is marked on the bar, named in the note and judged in the level.
v1.3.1 — 2026-09-08
- Small corrections after the first day of the new design (PR#39): the row of view tabs no
longer shows a scrollbar on the desktop; on the Repositories page a click anywhere in an
entry opens the repository, not only on its name; the footer carries Imprint and
Privacy on the right — the new
server.privacyUrljoinsserver.impressumUrl, both instance-level; and on the System page watch is a yellower amber and critical a clearly redder red, so the two are tellable.
v1.3.0 — 2026-09-08
- A new look (RFC 0001, PR#35): the web UI now shares the visual language of michael.hoennig.de — white paper, IBM Plex (bundled, nothing loaded from a third party), a blue-shifted petrol as the only accent, clay for failures, a neutral dark mode. Every page names its view first and explains it second; statuses are a dot and a word, icons are stroke glyphs instead of emoji; the logo is a W drawn as two check marks.
- The Repositories page (RFC 0001, PR#37): with several served repositories,
/repos— also behind the wordmark and the Repositories entry of the header — lists every repository with its newest build first, what is running or queued, the failed builds of the last seven days and whether the watcher reaches its origin, with filters for running, failed and unreachable. The repository pages carry a line naming the current repository and the conspicuous ones instead of the drop-down switcher;GET /api/reposserves the same data. A single-repository instance is unchanged. - The System page shows one gauge per configured filesystem (PR#33) — for example SSD and HDD on
a host whose artifacts live on a second disk — via
metrics.disksin the instance configuration, each with its own history, peaks and sizing verdict; the metrics state moved to~/.local/state/werkator/, taking an existing state file with it. - Capacity advice (PR#32): the System page says per resource whether the machine fits, should be upgraded or could be downsized, from thirty days of history.
v1.2.0 — 2026-09-03
- The build sandbox for hosts without Docker is configured as
werkdocknow, notbwrap(PR#19), and itsbwrap.werkdockkey — which named its own executor — iswerkdock.binary. The old section is still read, with a warning naming the file, so no installation has to be changed before its next configuration edit.bwrapnamed the mechanism one layer below the tool that actually runs it: builds have been executed by the werkdock CLI since v1.0.0.
v1.1.2 — 2026-09-03
- On a Hostsharing Managed Webspace, an
instance-updaterestart no longer looks dead while nothing is listening on the port for a moment: the generated.htaccess(PR#17) now maps a refused connection to a static "Werkator is restarting — please retry in a few minutes" page instead of Apache's default error page. Generated only where aserver.publicBaseUrlis configured, next to the existing.htaccess; nothing changes for a Docker-host deployment.
v1.1.1 — 2026-09-03
- The system page's disk metric is now quota-aware (PR#16): it shows the tightest of the user quota, the group quota, and the volume itself, instead of always the volume. On a Hostsharing Managed Webspace that is usually a group quota, far tighter than the shared host disk — the info line names the binding source and its hard limit, and the warn/critical highlighting now fires against the real budget. A host without a binding quota renders exactly as before.
v1.1.0 — 2026-09-03
- One instance can now serve several repositories (ADR 0009): a
~/.werkator.ymllists them, and each is worked on through its ownRepoContext— checkout, results, artifact store, and worktrees never share state across repositories. The watcher polls every registered repository in its own guard, and the executor'smaxConcurrentstill applies per(repository, branch), not just per branch. - Server routes carry the repository as
/repos/<name>/…and/api/repos/<name>/…; the unscoped routes keep meaning the served repository, so a single-repository installation notices nothing.build,retry, andstatustake a--repooption to pick a registered repository instead of always acting on the current directory. - The system page's resource metrics are sampled and reported per repository instead of once for the whole instance.
- The page title carries a drop-down next to the heading in place of the plain repository name once more than one repository is registered — picking a different one switches straight to the same view (Branches, History, …) on it. A single-repository installation still shows the plain name, unchanged.
v1.0.1 — 2026-08-31
- The restart button on the Branches view builds the branch's current head instead of repeating the commit its last build ran on, and says so: it reads "Build current head" there. A row on that page is a branch, not a past run — and repeating an overtaken commit can be worse than useless, because a build gate comparing the built commit against origin can never pass on it. The row keeps its build definition, and a branch that is gone from origin is refused by name instead of quietly falling back to the old commit.
- Latest and History are unchanged: a row there is a recorded run, and restarting it means that commit.
v1.0.0 — 2026-08-31
- GitTally is now Werkator. The old name already belongs to another
product in the git space, so the rename is a precaution and nothing more: what the
build system does, how it is configured, and how it reports to Gitea are unchanged.
In prose it is Werkator, everywhere a machine reads the name it is
werkator. The version marks the new name rather than a claim about maturity: the release after0.9.21is1.0.0, because a product that changes its name is better off counting from one under it. - The rename reaches the file names an installation depends on. The committed
configuration is
.werkator.yml, the machine-specific one.git/werkator/.werkator.yml, and all state — build results, artifacts, worktrees, the control token — lives under.git/werkator/. - The default Gitea check context is
werkator. Where the old name is pinned in a branch protection rule, the rule has to be updated with it, or a pull request waits forever for a check nobody posts any more. Statuses already written keep their old context, so a commit built before and after the change shows both.
v0.9.21 — 2026-08-30
- An origin the watcher cannot reach is now visible in the web UI: a banner above the table says that the list below is not updating, since when, and why. Until now the page kept showing its last known state, calm and plausible, while GitTally had not been able to fetch for an hour — the failure lived in the log alone. The banner is deliberately separate from the live indicator: that one says whether your browser reaches the server, this one whether the server reaches origin.
- A lasting fetch failure is logged when its message changes instead of on every poll
cycle, and the recovery is logged once. One wrong token used to write several hundred
identical warnings an hour. An invalid
atTimesslot is likewise reported once per slot.
v0.9.20 — 2026-08-29
- A build definition is now split in two: a
triggerblock says when the build runs and for which branches (onPush,atTimes,branches,activeWithin), everything beside it says what the build does. Only the second half is inherited frombuilds.default, and making that structural means a selector added later cannot become inheritable by accident. A definition still writing those keys flat is refused by name — a trigger nobody reads any more is a build that silently stops running. - A branch pattern prefixed with
!excludes instead of selecting, and an exclusion wins whatever the order:branches: ["*", "!master"]is every branch but master. That lets one branch have a build of its own without being built by the default one as well — previously the only way to avoid the double build was to give up the second definition's push trigger. - A build may report under its own Gitea check with
statusContext; empty keeps the repository-widegitea.statusContext. Two builds of one commit used to overwrite each other's result there, so a second build over the same branch — a quick check next to a long one — was not readable in Gitea. - Fixed: a branch whose builds all belong to named definitions showed an empty row in the branches view, reading as "never built" right next to its actual builds.
v0.9.19 — 2026-08-29
- A build definition now describes its build completely:
requirePullRequestand the wholedockersection, the sandbox policy included, live inbuilds.<name>.builds.defaultis the base every other definition inherits its settings from — never its trigger, becauseonPush,atTimes,branches, andactiveWithinsay when and where that one build runs. - The per-branch
branchessection is thereby superseded and will be removed. It is still read, but only while nothing defines a build at all: as soon as one real definition exists — a leftoverbuilds.maxConcurrentis not one —branchesis ignored completely and a warning names it. Either or, never both: two half-answers to what a build runs would pull against each other. requirePullRequest,docker.enabled, anddocker.networkstay pinned to the host, wherever a branch writes them. Since the inheritance is applied after all configuration layers are merged, a build a branch invents and the host has never heard of still inherits the host'sbuilds.default— it cannot reach a native build by defining a new job.- Fixed: the archived artifact directories came from the plain branch
settings, so a build definition adding its own
artifactDirs— a release job storingbuild/libs, say — never had them stored. - Fixed: the build definitions of a branch were cached by its head commit alone, so an edited machine or project configuration only took effect once that branch moved. On a quiet branch, a new scheduled job never started at all.
v0.9.18 — 2026-08-29
- A configuration file can declare which GitTally it is written for, so a version
that renames or drops a key says so instead of silently ignoring what it no longer
understands:
gitTally: { version: { since: "0.9.18", below: "2.0" } }.sinceis enforced — both against a GitTally that is too old and against a file written before the version in which the configuration format last changed incompatibly, which GitTally knows about itself.belowis a release marker and only warns, so an unmaintained value can never stop a build. - The reach follows the file: the machine and project configuration abort the start
naming the file and the way back, an incompatible configuration committed on a
branch fails only that branch's builds. A file that declares nothing keeps working,
and
gittally initwrites the current version into the config it generates.
v0.9.17 — 2026-08-29
- Fixed: a page left in the background — a phone tab switched away from for an hour — kept showing the state it was left in, with the durations of running builds counting up client-side although the builds had long finished. A returning page now fetches the current state immediately, whether the browser reports the return as a visibility change, a focus, or a restore from its cache.
v0.9.16 — 2026-08-29
- A scheduled build can run hourly:
atTimes: ["??:05"]stands for that minute of every hour. Each hour is its own slot, so it triggers once per hour — mixing it with fixedHH:MMtimes works, the latest due slot wins. Only the hour may be a wildcard; anything else is skipped with a warning. - Fixed: the artifact page showed the build command of the plain
branch configuration — for a build of a named definition, or on a branch whose
committed
.gittally.ymloverrides the command, that was a command the build never ran. It now shows what this build actually runs, resolved from the branch configuration committed at the build's own commit plus its definition.
v0.9.15 — 2026-08-29
- The
.gittally.ymlcommitted on a branch now takes precedence for thebuildssection as well — a branch can define its own build definitions and override those from the project config. The watcher reads each origin branch's committed configuration, so a new build definition takes effect by committing it on a branch, without touching any other branch's builds. A branch's definitions apply to that branch alone. - Pinned to the server side are only the keys that do not describe the branch's build:
secrets (
git), the host and repository sections (server,gitea,executor,watcher), the container sandbox policy (docker.enabled,docker.network), and therequirePullRequestgate. - Changed: the build concurrency limit moved from
builds.maxConcurrenttoexecutor.maxConcurrent(default 1, no compatibility alias) — thebuildssection now holds build definitions only. A leftoverbuilds.maxConcurrentkey is ignored with a warning instead of failing the configuration, so an installation keeps running until its committed config can be updated.
v0.9.14 — 2026-08-28
- Named build definitions (ADR 0007): the
buildssection of.gittally.ymlnow defines jobs withonPush/atTimestriggers, branch selectors (name globs,activeWithinage filter), and overrides of the branch settings — e.g. a nightlypitestbuild running a fuller check than the quick on-push builds. A named build records under its own<branch>@<build>pool with its own branches-view row, retention count, and permanent latest-green artifact link. - Restart,
gittally retry, and the startup recovery re-run a build under its recorded definition, resolving the settings from the current configuration. - Changed: the v0.9.13 per-slot
buildCommand/namesyntax inautoBuild.timesis gone again — use a build definition instead.branches.*.autoBuildwith plain times keeps working but is deprecated.
v0.9.13 — 2026-08-28
- A scheduled auto-build slot (
autoBuild.timesentry) can carry its ownbuildCommand, so a nightly slot runs a fuller check than the quick on-commit builds of the same branch. Restart, retry, and the startup recovery repeat a build with the command it originally ran. - A slot can also carry a
name(e.g.master@nightly): its builds then get their own row in the branches view, their own history and retention pool, and their own permanent latest-green artifact link (/branches/master_nightly/…) — the branch's regular builds no longer displace the nightly build and its artifacts.
v0.9.12 — 2026-08-26
- A build whose branch is deleted from origin mid-build (the usual fate of a merged branch) no longer vanishes from the UI and history while it is still queued or running — previously the queue looked stuck although a build was executing.
- Triggering a build that is already queued or running for the same commit no longer stacks a duplicate — an impatient double-click on restart now hits the existing build. Re-running a finished build is unaffected.
v0.9.11 — 2026-08-14
- At the end of each poll cycle, the local branch refs of the watched repository are
fast-forwarded to their state on origin, so build steps that compare a branch with its
origin counterpart no longer fail once origin moved on. Only fast-forwards are applied —
a branch that diverged from origin or is ahead of it stays untouched. Switched off with
watcher.fastForwardLocalRefs: false. - On the artifact index of a failed build, the logs that actually contain the build tool's failure line carry a failed badge — with stdout and stderr stored separately, that points straight at the log worth opening. Logs of a green build are not scanned.
v0.9.10 — 2026-08-11
- The control token is no longer embedded in the pages — reading them is unauthenticated,
so anyone could have picked it out of the HTML. Viewing stays public; the first click on
a restart/cancel/delete button asks for the token once (it is in
.git/gittally/control-tokenon the host) and keeps it in the browser.
v0.9.9 — 2026-08-11
- Changed default:
server.bindAddressis now127.0.0.1instead of0.0.0.0, because neither the UI nor the API authenticates read access. Existing.gittally.ymlfiles keep whatever they set; with the managed nginx container,0.0.0.0has to be set explicitly. - The control token is accepted in the
X-GitTally-Tokenheader only — thetokenquery parameter is gone, as URLs end up in access logs and browser history. config:printmasksgit.token;--show-secretsprints it.- Files holding secrets (the Gitea token written by
init, the control token) are created with mode0600right away instead of beingchmod-ed afterwards, the control token is compared in constant time, and git calls pass--before branch names. - On narrow screens the page title and the repository name are stacked instead of wrapping.
v0.9.8 — 2026-08-10
- The artifact index also links report pages of directories without an
index.html. A directory holding a single page is linked as a directory, so Gradle's--profilereport keeps a stable URL although its file name carries the build timestamp. - The permanent
🔗link now appears on the build it resolves to — the branch's latest green build — instead of on every build of that branch, and on all build tables. - The Current tab gave way to a
📡link in the artifacts column, shown while a build runs.
v0.9.7 — 2026-08-10
init --systemdalso installs a nightly Docker cleanup timer (gittally-docker-prune.timer, 02:00 host time): stopped containers and unused images are pruned before the auto builds — like the legacy host, but the per-repository Gradle cache volumes survive.
v0.9.6 — 2026-08-10
- This release-notes page, linked from the version in the footer.
- Mobile: the live indicator collapses to a colored state dot so it no longer squeezes the menu.
- The system page highlights critical utilization: the current value of CPU, RAM, and disk used turns orange from 80% and red from 90% of the respective total.
v0.9.5 — 2026-08-10
- Builds killed by a server shutdown (e.g. a deployment restart) are recorded as interrupted instead of failed, publish a pending Gitea status, and are re-enqueued when the server starts again.
v0.9.4 — 2026-08-10
- The legacy page names (
/index.html,/branches.html, …) answer with permanent redirects to the new routes, so pre-rewrite bookmarks and the redirect from the old host keep working.
v0.9.3 — 2026-08-10
- The artifact page shows a n failed badge behind links to test reports that contain failures.
v0.9.2 — 2026-08-10
- Cancelling a build also terminates its auxiliary phases (Docker image build, Gradle cache volume preparation), so the next queued build starts immediately.
v0.9.1 — 2026-08-10
- Durations no longer flicker while live-updating.
- While a build is running or pending, the artifact link shows an hourglass (logs only, so far) instead of the report icon.
- Pending builds show their queue wait time in italics; running and finished builds show the pure build time without the wait.
v0.9.0 — 2026-08-10
Port of the legacy gitTally bash script to Kotlin/Spring Boot — the first production deployment of the rewrite.
- Dual-mode application: CLI (
init,status,build,retry,config:print) and HTTP server with this web UI and a JSON API. - Declarative YAML configuration: committed
.gittally.ymlplus machine-specific overrides and secrets under.git/gittally/. - Builds run detached in git worktrees — the primary checkout is never used for builds.
- Docker build containers with automatic image rebuilds and a per-repository Gradle cache volume; works under rootless Docker daemons; git metadata is available read-only inside the container.
- Branch watcher with auto-builds (nightly slots), a pull-request gate, and pruning of results, artifacts, and stale worktrees.
- Commit statuses reported to Gitea; artifact store with per-branch retention and permanent latest-green links; system metrics page.
- Managed nginx/TLS container (Let's Encrypt) for hosts without a reverse proxy;
init --systemdgenerates the service unit. - Self-contained runtime bundle (jlink-trimmed JRE + jar) for hosts without a Java runtime.