Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed

- 🔌 **`Connection reset by peer` on client tool calls** — uvicorn's default keep-alive timeout (5s) can be shorter than a client's HTTP connection-pool reuse window, so a pooled-but-idle connection gets written to right after the server has already closed it, surfacing as `ConnectionResetError` even though the underlying command completed successfully. `timeout_keep_alive` is now configurable (`OPEN_TERMINAL_UVICORN_TIMEOUT_KEEP_ALIVE` or `uvicorn_timeout_keep_alive` in config.toml) and defaults to 75s.

## [0.11.34] - 2026-04-08

### Added
Expand Down
10 changes: 8 additions & 2 deletions open_terminal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,14 @@ def run(
click.echo(click.style(" └─────────────────────────────────────────────────────────────┘", fg="yellow"))
click.echo()

from open_terminal.env import UVICORN_LOOP
uvicorn.run("open_terminal.main:app", host=host, port=port, loop=UVICORN_LOOP)
from open_terminal.env import UVICORN_LOOP, UVICORN_TIMEOUT_KEEP_ALIVE
uvicorn.run(
"open_terminal.main:app",
host=host,
port=port,
loop=UVICORN_LOOP,
timeout_keep_alive=UVICORN_TIMEOUT_KEEP_ALIVE,
)


@main.command()
Expand Down
13 changes: 13 additions & 0 deletions open_terminal/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ def _resolve_file_env(var: str, default: str = "") -> str:
config.get("uvicorn_loop", "auto"),
)

# uvicorn's own default (5s) can be shorter than a client's HTTP
# connection-pool reuse window, so a pooled-but-idle connection can be
# written to right after the server has already closed it -- surfacing
# as a ConnectionResetError on the client even though the underlying
# command completed successfully. 75s matches gunicorn/nginx's common
# default and comfortably exceeds most client pool idle timeouts.
UVICORN_TIMEOUT_KEEP_ALIVE = float(
os.environ.get(
"OPEN_TERMINAL_UVICORN_TIMEOUT_KEEP_ALIVE",
config.get("uvicorn_timeout_keep_alive", 75),
)
)

OPEN_TERMINAL_INFO = os.environ.get(
"OPEN_TERMINAL_INFO",
config.get("info", ""),
Expand Down