iBotPeaches/Apktool · warning · BrutException
could not exec :
Error message
could not exec :
What it means
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.
Source
Thrown at brut.j.util/src/main/java/brut/util/OS.java:152
}
}
public static void exec(String[] cmd) throws BrutException {
try {
ProcessBuilder builder = new ProcessBuilder(cmd);
Process ps = builder.start();
new StreamForwarder(ps.getErrorStream(), "ERROR").start();
new StreamForwarder(ps.getInputStream(), "OUTPUT").start();
int exitValue = ps.waitFor();
if (exitValue != 0) {
throw new BrutException("Execution failed (exit code = " + exitValue + "): " + Arrays.toString(cmd));
}
} catch (IOException ex) {
throw new BrutException("could not exec: " + Arrays.toString(cmd), ex);
} catch (InterruptedException ex) {
throw new BrutException("could not exec : " + Arrays.toString(cmd), ex);
}
}
public static String execAndReturn(String[] cmd) {
ExecutorService executor = Executors.newCachedThreadPool();
try {
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.redirectErrorStream(true);
Process process = builder.start();
StreamCollector collector = new StreamCollector(process.getInputStream());
executor.execute(collector);
process.waitFor(15, TimeUnit.SECONDS);
executor.shutdownNow();
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
Log.w(TAG, "Stream collector did not terminate.");
}View on GitHub (pinned to 79b63384d7)
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
Example fix
// before
Future<?> f = pool.submit(() -> OS.exec(cmd));
f.cancel(true); // interrupts -> "could not exec : ..." and child keeps running
// after
// don't interrupt; bound the child instead
ProcessBuilder b = new ProcessBuilder(cmd);
Process p = b.start();
if (!p.waitFor(5, TimeUnit.MINUTES)) {
p.destroyForcibly();
throw new BrutException("tool timed out");
} Defensive patterns
Strategy: try-catch
Try / catch
try {
OS.exec(cmd);
} catch (BrutException e) {
if (e.getCause() instanceof InterruptedException) {
Thread.currentThread().interrupt(); // restore the flag the library swallowed
// kill any child process you tracked; the library leaves it running
cleanupChildProcesses();
throw new CancelledException("tool run interrupted", e);
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of iBotPeaches/Apktool@79b63384d7 (2026-08-14).
Data as JSON: /api/errors/8a11b7279c8e9078.
Report an issue: GitHub.