{"record":{"id":"8a11b7279c8e9078","repo":"iBotPeaches/Apktool","slug":"could-not-exec-8a11b7","errorCode":null,"errorMessage":"could not exec : ","messagePattern":"could not exec : ","errorType":"exception","errorClass":"BrutException","httpStatus":null,"severity":"warning","filePath":"brut.j.util/src/main/java/brut/util/OS.java","lineNumber":152,"sourceCode":"        }\n    }\n\n    public static void exec(String[] cmd) throws BrutException {\n        try {\n            ProcessBuilder builder = new ProcessBuilder(cmd);\n            Process ps = builder.start();\n\n            new StreamForwarder(ps.getErrorStream(), \"ERROR\").start();\n            new StreamForwarder(ps.getInputStream(), \"OUTPUT\").start();\n\n            int exitValue = ps.waitFor();\n            if (exitValue != 0) {\n                throw new BrutException(\"Execution failed (exit code = \" + exitValue + \"): \" + Arrays.toString(cmd));\n            }\n        } catch (IOException ex) {\n            throw new BrutException(\"could not exec: \" + Arrays.toString(cmd), ex);\n        } catch (InterruptedException ex) {\n            throw new BrutException(\"could not exec : \" + Arrays.toString(cmd), ex);\n        }\n    }\n\n    public static String execAndReturn(String[] cmd) {\n        ExecutorService executor = Executors.newCachedThreadPool();\n        try {\n            ProcessBuilder builder = new ProcessBuilder(cmd);\n            builder.redirectErrorStream(true);\n\n            Process process = builder.start();\n            StreamCollector collector = new StreamCollector(process.getInputStream());\n            executor.execute(collector);\n            process.waitFor(15, TimeUnit.SECONDS);\n            executor.shutdownNow();\n\n            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {\n                Log.w(TAG, \"Stream collector did not terminate.\");\n            }","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/iBotPeaches/Apktool/blob/79b63384d7d7e22917e6ea8b453272da7012515b/brut.j.util/src/main/java/brut/util/OS.java#L134-L170","documentation":"Thrown by OS.exec when ps.waitFor() is interrupted — i.e. the waiting thread got an interrupt (shutdown hook, timeout logic, thread pool cancellation) while the child process ran. Note the variant message with an extra space ('could not exec : ') and that the code does not re-interrupt the thread or destroy the child process, so the orphaned process may keep running.","triggerScenarios":"OS.exec(cmd) on a thread that another thread interrupts: executor shutdownNow(), a watchdog cancelling long-running external tools, or JVM shutdown hooks interrupting workers while aapt/zip is still executing.","commonSituations":"CI pipelines enforcing timeouts that interrupt build threads mid-tool-run; thread pools calling shutdownNow() during cleanup; user cancelling a build in an IDE; long-running external commands outliving their welcome because the interrupt path never destroys the process.","solutions":["Avoid interrupting threads that run OS.exec; use process-level timeouts (waitFor(t, unit)) or a paged design instead of thread interruption","In the catch block for BrutException, check whether the cause is InterruptedException and if so kill any child process your code tracks and restore the interrupt flag","Give external tools more headroom (bigger timeout, faster inputs) so they finish before watchdogs fire","If you control the caller, use OS.execAndReturn or your own ProcessBuilder wrapper that destroys the process on interrupt"],"exampleFix":"// before\nFuture<?> f = pool.submit(() -> OS.exec(cmd));\nf.cancel(true); // interrupts -> \"could not exec : ...\" and child keeps running\n\n// after\n// don't interrupt; bound the child instead\nProcessBuilder b = new ProcessBuilder(cmd);\nProcess p = b.start();\nif (!p.waitFor(5, TimeUnit.MINUTES)) {\n    p.destroyForcibly();\n    throw new BrutException(\"tool timed out\");\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    OS.exec(cmd);\n} catch (BrutException e) {\n    if (e.getCause() instanceof InterruptedException) {\n        Thread.currentThread().interrupt(); // restore the flag the library swallowed\n        // kill any child process you tracked; the library leaves it running\n        cleanupChildProcesses();\n        throw new CancelledException(\"tool run interrupted\", e);\n    }\n    throw e;\n}","preventionTips":["Never cancel tool-running tasks with thread interruption; bound the child with a timeout + destroyForcibly instead","Separate long external-tool calls onto threads you never interrupt (no shutdownNow on that pool)","Watch for the double-space 'could not exec :' message variant as the interrupt signature"],"tags":["process","exec","interruption","concurrency","java"],"backgroundTag":null,"analyzedSha":"79b63384d7d7e22917e6ea8b453272da7012515b","analyzedAt":"2026-08-14T10:43:28.812Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}