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
- Guard available() calls with a check that the socket is still open/connected.
- Catch IOException around available() and treat it as 'stream ended / 0 bytes'.
- Inspect the Error markdown (errno + errmsg) to find the native cause; EBADF/ECONNRESET indicate the peer closed or fd is stale.
- 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
- Do not call available() after close().
- Treat IOException from available() as a soft '0 bytes', not a fatal error, when probing.
- Inspect the Error markdown (errno) to distinguish peer-closed from transient errors.
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
- [dynamic] JniResult.getErrorString() — composed at runtime:
- Malformed symlink line:
- No SYMLINKS.txt encountered
- Moving termux prefix staging to prefix directory failed
- Invalid path:
AI-assisted analysis of termux/termux-app@3df69d1da1 (2026-08-13).
Data as JSON: /api/errors/843c2d073131a498.
Report an issue: GitHub.