MuntashirAkon/AppManager · error · IllegalStateException

Unable to add commands to a closed shell.

Error message

Unable to add commands to a closed shell.

What it means

Shell is a queue-based command shell. add() is the internal path exec() uses to enqueue a Command; if the shell has been closed (mClosed == true, e.g. after exit/finish or the underlying process died) it throws IllegalStateException instead of queueing into a dead shell. It is an invalid-state error: the caller must not submit commands to a terminated shell.

Source

Thrown at libserver/src/main/java/io/github/muntashirakon/AppManager/server/common/Shell.java:186

     */
    public boolean allCommandsOver() {
        return mCommandQueue.isEmpty();
    }


    private int generateCommandID() {
        int id = mNextCmdID.getAndIncrement();
        if (id > 0x00FFFFFF) {
            mNextCmdID.set(1);
            id = generateCommandID();
        }
        return id;
    }

    @NonNull
    private Command add(Command command) {
        if (mClosed) {
            throw new IllegalStateException("Unable to add commands to a closed shell.");
        }
        command.setId(generateCommandID());
        mCommandQueue.offer(command);
        return command;
    }

    @NonNull
    public Result exec(String cmd) {
        Result result = new Result();
        FLog.log("Command:  " + cmd);
        final StringBuilder outLine = new StringBuilder();
        try {
            result.mStatusCode = add(new Command(cmd) {
                @Override
                public void onUpdate(int id, String message) {
                    outLine.append(message).append('\n');
                }

View on GitHub (pinned to 0152f468fc)

Solutions

  1. Create a new Shell instance instead of reusing the closed one (check shell.isClosed() before exec if available).
  2. Serialize access: ensure no code path calls exit()/close() while commands are still being submitted (guard with lifecycle checks).
  3. If the shell closed because the su process died, re-request root and reopen the shell before running further commands.
  4. Wrap exec() in try-catch for IllegalStateException and recreate the shell as recovery.

Example fix

// before
shell.exec(cmd); // throws if closed
// after
if (shell.isClosed()) {
    shell = new Shell.Builder().build(); // or recreate via your shell provider
}
shell.exec(cmd);
Defensive patterns

Strategy: type-guard

Validate before calling

if (shell == null || shell.isClosed()) {
    shell = createNewShell(); // recreate before submitting commands
}

Type guard

boolean usable(Shell s) {
    return s != null && !s.isClosed();
}

Try / catch

try {
    Result r = shell.exec(cmd);
} catch (IllegalStateException e) {
    shell = createNewShell();
    Result r = shell.exec(cmd); // single retry on fresh shell
}

Prevention

When it happens

Trigger: Calling shell.exec(cmd) — or any code path that reaches add() — after shell.exit()/close() has been invoked, or after the shell's process has terminated and marked itself closed; notably calling exec() on a Shell instance that a previous exec() already terminated (e.g. an 'exit' command).

Common situations: Reusing a long-lived Shell object after a script ran 'exit'; executing a command after the su session was killed by the system or root manager; running exec() from another thread concurrently with shell shutdown; holding a Shell across a configuration change/restart in the app.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of MuntashirAkon/AppManager@0152f468fc (2026-09-12). Data as JSON: /api/errors/b78c62862035e4df. Report an issue: GitHub.