{"record":{"id":"073ffb02ea5043a1","repo":"floci-io/floci","slug":"hook-script-failed-s-exited-with-code-d","errorCode":null,"errorMessage":"Hook script failed: %s exited with code %d","messagePattern":"Hook script failed: (.+?) exited with code (.+?)","errorType":"panic","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/lifecycle/inithook/HookScriptExecutor.java","lineNumber":40,"sourceCode":"\n    public void run(final File scriptFile) throws IOException, InterruptedException {\n        run(scriptFile.getParentFile(), scriptFile.getName());\n    }\n\n    public void run(final File hookDirectory, final String scriptFileName) throws IOException, InterruptedException {\n        final String command = scriptFileName.endsWith(\".py\") ? \"python3\" : initHooksConfig.shellExecutable();\n        LOG.debugv(\"Executing hook script {0} via {1}\", scriptFileName, command);\n\n        // Inherit parent I/O so script output is streamed directly and does not block on unconsumed buffers.\n        final Process process = new ProcessBuilder(command, scriptFileName).directory(hookDirectory).inheritIO().start();\n        run(process, scriptFileName);\n    }\n\n    void run(final Process process, final String scriptFileName) throws InterruptedException {\n        final int exitCode = waitForProcessExitCode(process, scriptFileName);\n        if (exitCode != 0) {\n            final String message = String.format(\"Hook script failed: %s exited with code %d\", scriptFileName, exitCode);\n            throw new IllegalStateException(message);\n        }\n    }\n\n    private int waitForProcessExitCode(final Process process, final String scriptFileName) throws InterruptedException {\n        try {\n            final long timeoutSeconds = initHooksConfig.timeoutSeconds();\n            final boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);\n            if (!finished) {\n                LOG.debugv(\"Hook script exceeded timeout of {0} seconds, terminating process: {1}\", timeoutSeconds, scriptFileName);\n                terminateProcess(process, scriptFileName);\n\n                final String message = String.format(\"Hook script timed out after %d seconds: %s\", timeoutSeconds, scriptFileName);\n                throw new IllegalStateException(message);\n            }\n\n            return process.exitValue();\n        } finally {\n            if (process.isAlive()) {","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/lifecycle/inithook/HookScriptExecutor.java#L22-L58","documentation":"Thrown by Floci's init-hook executor when a lifecycle hook script (shell or Python) runs to completion but exits with a non-zero status code. Floci runs user-supplied hook scripts from the configured hook directory during emulator startup; a non-zero exit is treated as a hard startup failure via IllegalStateException. The message names the offending script file and its exit code.","triggerScenarios":"A file in the Floci init-hooks directory (e.g. *.sh or *.py) exits non-zero when the emulator starts. Typical cases: a shell script missing 'set -e' that fails on its last command, a python3 script raising an uncaught exception, a script referencing tools absent from the image (awscli, curl), or a script returning the exit code of a failed curl/health check against a service that is not up yet.","commonSituations":"Custom provisioning hooks written for one environment breaking in another (missing binary, different shell), scripts that assume network access in an offline CI runner, Python scripts that work under 'python' but the executor invokes 'python3' (or vice versa), and hooks that depend on services Floci starts later so ordering assumptions break.","solutions":["Run the hook manually with the same interpreter Floci uses (sh script.sh / python3 script.py) and fix the failing command","Add 'set -euo pipefail' (shell) or explicit exit codes (Python) so failures happen at the real failing line and are visible","Verify every binary the script invokes exists in the container/host where Floci runs","If the failure is an ordering issue, make the script wait/retry on the dependency or move it to a later hook stage","If the hook is optional, guard failures with explicit 'exit 0' after logging, or remove it from the hooks directory"],"exampleFix":"# before (last command's failure propagates silently or unexpected code)\ncurl http://localhost:4566/_localstack/health\n\n# after (explicit, logged, deliberate exit code)\nif ! curl -sf http://localhost:4566/ > /dev/null; then\n  echo \"hook: emulator not reachable\" >&2\n  exit 1\nfi","handlingStrategy":"validation","validationCode":"# verify the hook passes before letting Floci run it\nsh -n hooks/01-setup.sh && sh hooks/01-setup.sh; echo \"exit=$?\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run every hook manually with the same interpreter (sh/python3) before adding it to the hooks directory","Start every shell hook with 'set -euo pipefail' and end Python hooks with explicit sys.exit codes","Make every network call in a hook use a bounded timeout (curl --max-time, timeout(1))"],"tags":["lifecycle","init-hooks","startup","shell","process"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}