Kai Ole Hartwig
6 Min. Lesezeit

Warum Renovate unsere eigenen Pakete nie aktualisiert hat — eine Spurensuche

Ich betreibe mehrere TYPO3-14-Installationen, jede als eigenes App-Repo mit einem Dutzend eigener moselwal/*-Composer-Pakete plus geforkten Extensions. Jedes Mal, wenn ich ein Sitepackage releaste, musste ich den Versions-Bump im App-Repo von Hand nachziehen — composer update, Commit, Merge. Renovate lief zuverlässig für externe Pakete, rührte meine eigenen aber nie an. Die naheliegende Annahme: eine geplante Fast-Lane-Konfiguration fehlt noch. Die Realität lag vier Schichten tiefer — und jede Schicht verdeckte die nächste.

TL;DR

TL;DR: Vier ineinander verschachtelte Ursachen verhinderten, dass Renovate meine first-party moselwal/*-Pakete je gebumpt hat: ein custom.regex-Manager, der Caret-Ranges als „schon aktuell“ liest; derselbe Regex-Manager, der die composer.lock nie anfasst; ein Renovate-Container, in dem composer unter dem GitLab-docker-executor gar nicht installiert war; und, als Bonus, ein ungefilterter Registry-Eintrag, der jede externe Paketanfrage über einen 404-Umweg zur eigenen GitLab-Registry schickte. Der Fix: first-party-Deps exakt pinnen, einen postUpgradeTask für die Lock-Aktualisierung ergänzen, composer explizit im before_script installieren, und die Registry mit einem only-Filter versehen.

Was passiert ist

Das Symptom

Renovate lief bei mir zuverlässig — für externe Pakete. Interne Updates blieben aus. Immer wenn ich ein Sitepackage releaste, musste ich den Bump im App-Repo von Hand nachziehen: composer update, Commit, Merge, jedes Mal. Die naheliegende Annahme war, dass die geplante Fast-Lane für eigene Pakete schlicht noch nicht ausgerollt war. Die Realität steckte vier Schichten tiefer, jede für sich plausibel, jede die nächste verdeckend.

Ursache 1: Caret-Ranges sind für den Regex-Manager unsichtbar

Meine moselwal/*-Pakete liegen in GitLabs gruppenweiter Composer-Registry. Deren Endpoint liefert am p2/-Pfad die Versionen als objekt-keyed Dict statt als Array — Renovates packagist-Datasource parst das nicht. Der frühere Autor hat das umschifft: ein custom.regex-Manager liest die Versionen direkt aus den Git-Tags.

Klug — aber mit einer Falle. Ein Regex-Manager liest die composer.lock nicht. Er sieht nur "moselwal/wehning-site": "^1.0" in der composer.json. Und für einen Caret-Range ist die „aktuelle Version“ definitionsgemäß die höchste passende — also 1.8.0, die neueste. Es gibt nichts zu bumpen. updates: [], jedes Mal, egal welche rangeStrategy.

Ein isolierter renovate --dry-run bestätigte es schwarz auf weiß: currentVersion: 1.8.0, updates: [].

 

# Vorher — für einen Regex-Manager unsichtbar
"moselwal/wehning-site": "^1.0"

# Nachher — exakt gepinnt, Regex-Manager sieht 1.6.0 < 1.8.0
"moselwal/wehning-site": "1.6.0"

 

Fix: first-party-Deps exakt pinnen ("1.6.0" statt "^1.0"). Jetzt sieht der Regex-Manager 1.6.0 < 1.8.0 und erzeugt ein Update.

Alternative für andere Setups: Wer die Caret-Range nicht aufgeben will, kann dem Regex-Manager stattdessen ein currentValueTemplate geben, das die tatsächlich installierte Version aus der composer.lock extrahiert, statt die rohe Range aus der composer.json zu übernehmen. Für mich war exaktes Pinnen der einfachere Weg — beide Wege lösen dasselbe Grundproblem: der Regex-Manager braucht eine echte, konkrete „aktuelle Version“ zum Vergleichen, keine Range.

Ursache 2: Der Regex-Manager schreibt die Lock nicht

Exact-Pin gesetzt — und Renovate legte tatsächlich einen Branch an, der composer.json von 1.6.0 auf 1.8.0 bumpte. Aber die composer.lock blieb unberührt. Content-Hash-Mismatch → composer install schlägt fehl → Branch-Pipeline rot → kein Auto-Merge.

Der Grund: Renovate ruft composer updateArtifacts (das die Lock nachzieht) nur für den composer-Manager auf, nicht für custom.regex. Der Bump kam vom Regex-Manager → die Lock blieb liegen.

 

postUpgradeTasks:
  commands:
    - "composer update {{{depName}}} --no-install --ignore-platform-reqs"
  fileFilters:
    - composer.lock
  executionMode: branch

 

Fix: ein postUpgradeTask an der first-party-Regel: composer update <dep> --no-install …, plus das Kommando in RENOVATE_ALLOWED_COMMANDS whitelisten.

Ursache 3: composer existiert im Renovate-Container gar nicht

Der postUpgradeTask lief — und scheiterte an spawn composer ENOENT. Composer war nicht installiert. Renovate installiert Tools normalerweise on-demand via containerbase. Nur: unter dem GitLab-docker-executor, der den Image-Entrypoint überschreibt, triggert dieser on-demand-Install nicht. Kein Installing tool-Log, nichts — Renovate versuchte composer direkt aus dem PATH zu starten, wo es nicht lag. Auch RENOVATE_BINARY_SOURCE=install änderte daran nichts.

Das war der eigentliche Hammer: Es erklärt, warum Renovate auf keinem meiner Repos je einen composer-Lock aktualisiert hat — nicht die eigenen Pakete, und auch die externen nicht.

 

before_script:
  - install-tool php
  - install-tool composer

 

Fix: die Tools explizit im before_script installieren. containerbase zog in ~3 Sekunden PHP 8.5.8 und Composer 2.10.2. ENOENT verschwand.

Ursache 4 (der Bonus): der 404-Sturm

Am Rande bin ich über die zweite Wurzel der Langsamkeit gestolpert. Meine composer.json listete die GitLab-Registry ohne Filter — also fragte composer sie für jedes Paket an, auch die hunderten externen. Jedes ergab einen 404-Roundtrip zur (RAM-knappen) GitLab-Box, bevor composer auf packagist auswich.

 

{
  "repositories": [
    {
      "type": "composer",
      "url": "https://gitlab.example.com/api/v4/group/123/-/packages/composer/packages.json",
      "only": [
        "moselwal/*",
        "netresearch/nr-passkeys-be",
        "sgalinski/*"
      ]
    }
  ]
}

 

Fix: ein only-Filter auf die Registry — composer fragt sie nur noch für first-party und Forks. Die Tücke: geforkte externe Pakete (netresearch/nr-passkeys-be, sgalinski/*) behalten ihren Upstream-Namen und müssen in only stehen, sonst holt composer still den Upstream statt des Forks. Über-Inklusion ist harmlos (404→Fallback), Unter-Inklusion bricht Forks.

Ein Gegenbeispiel lieferte ich dabei gleich mit: Ein Commit-Tool, das JSON-Newlines zerstörte, legte meine Renovate-Runner-Config zweimal lahm. Robuste Commits schlagen bequeme.

Die Lehre

Was mich durch vier verschachtelte Ursachen trug, war nicht Raten, sondern isolierte renovate --dry-run-Läufe und das konsequente Lesen der Job-Logs — jede Hypothese einzeln belegt, bevor an der geteilten Config geschraubt wurde. Ein Custom-Regex-Manager ist ein Workaround mit eigenen blinden Flecken: Er liest nur, was Sie ihm zu lesen sagen — nie die Lock-Datei, nie den vollen Bump-Zyklus, den der native Manager stillschweigend mitbringt.

Häufige Fragen

Ist ein zu breiter only-Filter auf der Registry gefährlich?+

Über-Inklusion ist harmlos — composer bekommt einfach einen 404 und fällt auf packagist zurück. Gefährlich ist Unter-Inklusion: Geforkte Pakete behalten ihren Upstream-Namen, und ohne Eintrag in only holt composer still den Upstream statt des eigenen Forks — genau das Gegenteil von harmlos.

Warum meldet Renovate nicht direkt, dass composer fehlt?+

Weil der postUpgradeTask einfach mit spawn composer ENOENT fehlschlägt und containerbases on-demand-Install unter dem GitLab-docker-executor, der den Image-Entrypoint überschreibt, gar nicht erst auslöst. Kein Installing tool-Log, kein Hinweis — nur ein leiser Fehlschlag des Branches.

Warum genügt es nicht, einfach rangeStrategy auf pin zu setzen?+

Weil das Problem nicht die rangeStrategy ist, sondern dass ein Regex-Manager überhaupt kein Konzept von „aktueller vs. gewünschter Version“ jenseits des Caret-Ranges selbst hat. Bei ^1.0 ist 1.8.0 formal „aktuell“, egal welche Strategie gesetzt ist — die Range muss exakt gepinnt sein, damit der Manager überhaupt eine Differenz sieht.

Fazit

Vier Ursachen, jede für sich plausibel, jede die nächste verdeckend: ein Regex-Manager, der Caret-Ranges als bereits aktuell liest; derselbe Manager, der nie eine Lock-Datei schreibt; ein fehlendes composer-Binary im Renovate-Container; und ein ungefilterter Registry-Eintrag mit Fork-Risiko. Am Ende stehen fünf zentrale Zeilen in der geteilten Renovate-Config und zwei kleine Änderungen pro Consumer-Repo — und die eigenen Pakete fließen jetzt genauso automatisch wie die externen.

Bevor der nächste Sitepackage-Release wieder zum manuellen composer update wird — sprechen wir über Ihre Renovate-Config.

Ich finde die vier Schichten, die Ihre Renovate-Config lahmlegen, bevor Sie den nächsten Bump wieder von Hand nachziehen müssen.

Isolierte renovate --dry-run-Diagnose gegen Ihre tatsächliche Config, Prüfung von custom.regex-Managern auf blinde Flecken bei Lock-Dateien und Caret-Ranges, sowie ein Blick in den Renovate-Container selbst — bevor der nächste Release-Bump wieder manuell passiert.

Plattform-Betrieb statt Beratung auf Papier: Ich baue und pflege Ihre CI/CD- und Dependency-Update-Pipelines laufend.

Termin direkt vereinbaren

Über den Autor

Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freiberuflicher DevSecOps-Berater · OnlyOle Consulting

Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.