Skip to content

Harden the PTY proxy, add a session summary, and support Python 3.10+ - #1

Merged
Vaibhavtripathi7 merged 13 commits into
mainfrom
hardening
Jun 20, 2026
Merged

Vaibhavtripathi7 merged 13 commits into
mainfrom
hardening

Conversation

@Vaibhavtripathi7

@Vaibhavtripathi7 Vaibhavtripathi7 commented Jun 20, 2026 •

Copy link
Copy Markdown
Owner

Overview

Reliability and security hardening of the PTY interceptor, a visible end-of-session savings summary, wider Python support, and full docs.

Changes

  • Added an end-of-session summary showing chunks pruned, tokens saved, an estimated cost, and elapsed time.
  • The terminal is now restored and the child is signalled when ACK is killed with SIGTERM or SIGHUP.
  • Fixed a 100% CPU busy-loop that occurred when stdin reached EOF or the agent sat idle.
  • A traceback that spans several PTY reads is now held and pruned as one unit instead of being half-compressed.
  • Injected summaries use CRLF in raw mode, so they no longer print as a staircase.
  • ACK now exits with code 127 and a clear message when the wrapped command does not exist.
  • Stored output is stripped of terminal escape sequences before it is printed, closing a terminal-hijack vector.
  • The session summary no longer crashes when its output is piped to a consumer that closes early.
  • Each prunable block is now matched once instead of twice.
  • Lowered the supported Python floor to 3.10 and run CI across 3.10-3.13.
  • Corrected the repository URL in the package metadata.
  • Added a complete README with a demo GIF and an architecture diagram.

Impact

  • Idle/piped CPU: ~98% to ~0%.
  • Tracebacks and floods compress cleanly end to end; ~95% token compression at 100% signature fidelity.
  • Installable on Python 3.10-3.13 (was 3.13 only).
  • Closes a terminal-hijack vector from displaying stored output.

Tests

  • 97 tests pass (unit + integration).
  • ruff + mypy --strict clean.
  • Compression benchmark green; CI across Python 3.10-3.13.

The repository field pointed at a non-existent slug; update it to the
actual GitHub remote so clone instructions and PyPI links resolve.
ACK uses no 3.11+ syntax, so lower the floor from 3.13 to 3.10 to widen
the installable audience. Add the 3.10-3.12 trove classifiers, retarget
ruff and mypy to py310, refresh the lock file, and run CI across
3.10-3.13.
When the wrapped agent exits, print a short summary to stderr: chunks
intercepted/pruned, tokens saved (with a rough cost estimate), and
elapsed time. Makes the value of a session visible at a glance. Skipped
under --no-annotate and in the TUI (which shows live stats).
The terminal's NL->CRLF mapping (OPOST) is disabled while ACK holds the
TTY in raw mode, so the bare newlines in our injected summaries printed
as a staircase. Child passthrough already carries CRLF from its own PTY,
so convert only the text ACK generates, and only while raw.
A traceback larger than the kernel's PTY buffer arrives in several reads.
ACK was pruning the first fragment (no exception line yet) and passing
the rest through raw. Hold a buffer whose traceback has not reached its
exception line until it completes (bounded by max_buffer_bytes), and
condense a repetitive preamble that shares the buffer with it.
With stdin at EOF (piped input, /dev/null, CI) select reported it
readable forever, burning ~100% CPU. Drop stdin from the watch set once
it reaches EOF, and block in select() with no timeout while nothing is
buffered so the loop is idle at ~0% CPU instead of polling.
execvp raised straight through the forked child, dumping a Python
traceback and a misleading exit code. Catch the OSError, print a short
'cannot run ...' message, and exit 127.
If ACK was killed by a signal the shell was left stuck in raw mode and
the child orphaned. Install handlers that restore the terminal, forward
the signal to the child's process group, and exit 128+signum. Guard the
install for the main thread only (the --tui worker thread cannot set
signal handlers), which also fixes a latent crash in that mode.
The flush loop called pruner.matches() and then compress(), but compress()
already self-gates with matches() internally. Call compress() directly so
a pruned block is matched once instead of twice.
When ACK output is piped to a consumer that exits early (e.g.
'... 2>&1 | head'), the summary's echo hit a BrokenPipeError. Guard it
so a closed downstream never masks the agent's real exit code.
ack search/sessions echoed stored raw_content verbatim, which still holds
the agent's original escape sequences. A captured mouse/app-mode toggle
could reprogram the reader's terminal (runaway output, dead Ctrl-C).
Sanitise escape and control characters before printing; raw bytes stay
untouched in the database.
Rework the example into a paced build-and-test simulation: status lines
pass through, a 200-line log flood collapses to a frequency table, and a
deep traceback buried under framework frames collapses to the exception
plus the user frames. Short pauses keep each phase readable.
@Vaibhavtripathi7
Vaibhavtripathi7 merged commit 9d9fb07 into main Jun 20, 2026
4 checks passed
@Vaibhavtripathi7
Vaibhavtripathi7 deleted the hardening branch June 20, 2026 08:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant