termux/termux-app · error · IOException

[dynamic] Error.getErrorMarkdownString() — composed at runti

Error message

[dynamic] Error.getErrorMarkdownString() — composed at runtime: "**Error Code**: {code}\n**Error Message ({type})**: {message}\n\n{stack traces}"

What it means

Thrown by SocketInputStream.available() when LocalClientSocket.available() returns a non-null Error. The Error is rendered to a markdown string (Error Code / Error Message / stack traces) and wrapped in an IOException. available() on a local (Unix domain) socket delegates to a JNI call; any native failure (bad fd, EBADF, peer closed) surfaces here.

Source

Thrown at termux-shared/src/main/java/com/termux/shared/net/socket/local/LocalClientSocket.java:452

            MutableInt bytesRead = new MutableInt(0);
            Error error = LocalClientSocket.this.read(bytes, bytesRead);
            if (error != null) {
                throw new IOException(error.getErrorMarkdownString());
            }

            if (bytesRead.value == 0) {
                return -1;
            }

            return bytesRead.value;
        }

        @Override
        public int available() throws IOException {
            MutableInt available = new MutableInt(0);
            Error error = LocalClientSocket.this.available(available);
            if (error != null) {
                throw new IOException(error.getErrorMarkdownString());
            }
            return available.value;
        }
    }



    /** The {@link OutputStream} implementation for the {@link LocalClientSocket}. */
    protected class SocketOutputStream extends OutputStream {
        private final byte[] mBytes = new byte[1];

        @Override
        public void write(int b) throws IOException {
            mBytes[0] = (byte) b;

            Error error = LocalClientSocket.this.send(mBytes);
            if (error != null) {
                throw new IOException(error.getErrorMarkdownString());

View on GitHub (pinned to 3df69d1da1)

Solutions

  1. Guard available() calls with a check that the socket is still open/connected.
  2. Catch IOException around available() and treat it as 'stream ended / 0 bytes'.
  3. Inspect the Error markdown (errno + errmsg) to find the native cause; EBADF/ECONNRESET indicate the peer closed or fd is stale.
  4. Ensure you do not read after the socket has been closed by the manager.

Example fix

// before
public int available() throws IOException {
    MutableInt available = new MutableInt(0);
    Error error = LocalClientSocket.this.available(available);
    if (error != null) {
        throw new IOException(error.getErrorMarkdownString());
    }
    return available.value;
}

// after (graceful: treat native error as 0 available on closed peer)
public int available() throws IOException {
    MutableInt available = new MutableInt(0);
    Error error = LocalClientSocket.this.available(available);
    if (error != null) {
        Logger.logDebug(LOG_TAG, "available() error: " + error.getErrorLogString());
        return 0;
    }
    return available.value;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!socket.isConnectedOrOpen()) {
    return 0; // no bytes available
}

Try / catch

try {
    return inputStream.available();
} catch (IOException e) {
    // treat native available() failure as 'stream ended'
    Log.d(TAG, "available() failed, assuming 0: " + e.getMessage());
    return 0;
}

Prevention

When it happens

Trigger: The underlying socket file descriptor is closed or invalid; the peer has closed the connection; a JNI/system call in available() returns an errno (e.g. EBADF, ENOTSOCK); the socket was never successfully connected.

Common situations: Calling available() after close(); peer process exited mid-stream; fd invalidated by an error on a prior read/write; using the stream after the socket manager tore it down.

Related errors


AI-assisted analysis of termux/termux-app@3df69d1da1 (2026-08-13). Data as JSON: /api/errors/843c2d073131a498. Report an issue: GitHub.