register_argc_argv=Off hat mich erwischt: php -r in einem gehärteten CI-Image debuggen
Ein php -r-Einzeiler, der auf jedem Laptop funktioniert und in CI stirbt. Die Ursache ist ein php.ini-Flag, das man vermutlich noch nie bewusst gesetzt hat: register_argc_argv. Dieser Beitrag ist Teil 1 von „The Ops Log“ — vier Vorfälle aus dem Betrieb einer selbst gehosteten GitLab-CI- + TYPO3/FrankenPHP-Plattform im Ein-Personen-Setup, jeder mit einer Lehre, die den Vorfall überlebt.
TL;DR
TL;DR: Gehärtete Container-Images liefern register_argc_argv=Off aus. Ist das Flag aus, ist $argv innerhalb von php -r null — php -r '…$argv…' file verliert sein Argument also lautlos. Daten stattdessen über stdin übergeben; das hängt nicht von der ini ab.
Was passiert ist
Ich war dabei, acht Lint-Jobs zu einem zusammenzulegen (das ist Teil 2 dieser Serie). Einer der zusammengefalteten Checks validiert JSON. Wenn das jsonlint-Binary im Repo nicht installiert ist, hatte ich einen Fallback ergänzt, der einmal pro Datei auf PHPs eigenen Parser umschaltet:
check_json — erster Versuch
# jede JSON-Datei finden, einzeln an php -r übergeben
find . -name '*.json' -exec php -r '
foreach (array_slice($argv, 1) as $f) {
json_decode(file_get_contents($f));
if (json_last_error() !== JSON_ERROR_NONE) { exit(1); }
}' {} +
Auf meinem Rechner grün. In der CI ging der Job rot, mit einer Meldung, die für gerade erst getesteten Code keinen Sinn ergab:
Job-Trace
PHP Fatal error: Uncaught TypeError: array_slice():
Argument #1 ($array) must be of type array, null given
in Command line code:2
$argv war null. Nicht leer — null. Auf einem Entwicklungsrechner befüllt die CLI-SAPI $argv standardmäßig, weshalb man es nie bemerkt. Das goldene CI-Image basiert auf einem gehärteten Base-Image mit register_argc_argv=Off in der php.ini — eine verbreitete „wir brauchen kein argv in Produktion“-Härtung. Mit abgeschaltetem Flag existieren $argv und $argc schlicht nicht, und php -r kann den übergebenen Dateinamen nicht sehen.
Das Ganze lässt sich in drei Zeilen reproduzieren — ganz ohne Container:
Reproduktion
$ php -r 'var_dump($argv);' hello.json
array(2) { [0]=> "Standard input code" [1]=> "hello.json" } ✓ funktioniert
$ php -d register_argc_argv=Off -r 'var_dump($argv);' hello.json
NULL ✗ null
Der Fix ist nicht, das Flag mit -d register_argc_argv=1 zurückzudrehen — das ist zerbrechlich, man bekämpft damit das Image. Der Fix ist, gar nicht erst von argv abhängig zu sein. Die Dateiliste NUL-getrennt über stdin einspeisen und mit stream_get_contents(STDIN) lesen:
check_json — der Fix
find . ! -path '*/vendor/*' -name '*.json' -print0 \
| php -r '$bad=0;
foreach (array_filter(explode("\x00", stream_get_contents(STDIN)), "strlen") as $f) {
json_decode(file_get_contents($f));
if (json_last_error() !== JSON_ERROR_NONE) {
fwrite(STDERR, "✗ invalid JSON: $f — ".json_last_error_msg()."\n"); $bad=1;
}
} exit($bad);'
Verifiziert, indem ich die exakte CI-Bedingung lokal simuliert habe — -d register_argc_argv=Off —, damit es nicht auf demselben Weg regressieren kann. Ein gültiges Set beendet sich mit 0; eine fehlerhafte Datei mit 1 und einer lesbaren Zeile. Nebenbefund oben im ersten Versuch: Der erste Anlauf scannte auch vendor/, wo Abhängigkeiten absichtlich fehlerhafte JSON-Fixtures ausliefern — dessen Ausschluss war die halbe Miete.
Die Lehre
php -rplus$argvist eine Portabilitäts-Falle: Es stützt sich auf einen php.ini-Default, den gehärtete Images abschalten. Alles, was in einem fremden Container laufen soll, sollte seine Eingabe über stdin bekommen, nicht über argv.
Häufige Fragen
Betrifft das nur den JSON-Lint-Fallback aus diesem Beispiel?+
Nein — jedes php -r-Snippet, das sich auf $argv/$argc verlässt, ist in einem Image mit register_argc_argv=Off betroffen. Der JSON-Lint-Fallback ist nur das konkrete Beispiel, an dem es sichtbar wurde.
Reicht es, register_argc_argv wieder einzuschalten?+
Technisch ja, mit -d register_argc_argv=1, aber das bekämpft das gehärtete Image statt mit ihm zu arbeiten — bei jedem Image-Update kann die Einstellung wieder verschwinden. Der robustere Fix ist, den Input über stdin statt argv zu beziehen.
Warum bemerkt man das Problem nicht auf dem eigenen Rechner?+
Weil die CLI-SAPI $argv auf Entwicklungsmaschinen standardmäßig befüllt — register_argc_argv=Off ist typischerweise nur in gehärteten Produktions-/CI-Images gesetzt, nicht lokal.
Fazit
Ein einzelnes php.ini-Flag, das kaum jemand bewusst setzt, hat aus einem funktionierenden Einzeiler einen stillen CI-Blocker gemacht. Die Lehre reicht über diesen Fall hinaus: Jeder Code, der in einem fremden, gehärteten Container laufen soll, sollte seine Eingaben so entgegennehmen, dass er von Defaults wie diesem unabhängig ist.
Ich härte und debugge Ihre CI-Pipelines auf gehärteten Base-Images — von PHP-Eigenheiten bis zu GitLab-Runner-Konfiguration.
Audit Ihrer CI-Jobs auf stille Abhängigkeiten von php.ini-Defaults, Umgebungsvariablen und Image-Annahmen, plus Migration auf robustere, image-unabhängige Muster.
Plattform-Betrieb statt Beratung auf Papier: Ich baue und härte Ihre CI/CD-Pipelines laufend.
Über den Autor

Kai Ole Hartwig
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.
