Skip to content

feat: session prompt, clipboard fix, output batching, and dep cleanup#119

Merged
datasciencemonkey merged 21 commits intomainfrom
feat/session-creation-prompt
Apr 14, 2026
Merged

feat: session prompt, clipboard fix, output batching, and dep cleanup#119
datasciencemonkey merged 21 commits intomainfrom
feat/session-creation-prompt

Conversation

@datasciencemonkey
Copy link
Copy Markdown
Owner

@datasciencemonkey datasciencemonkey commented Apr 14, 2026

Summary

Closes #118, closes #120, closes #121, closes #122

Addresses multiple issues discovered during investigation of a user-reported crash on the coda Databricks App:

1. OOM crash prevention (#118)

The app crashed suddenly with clean logs on a Medium instance (6GB RAM / 2 vCPUs) — pointing to an OOM kill. Users unknowingly accumulated terminal sessions (each with PTY + bash + AI agent subprocesses) until the container exceeded its memory limit.

2. Copy-paste broken inside Claude Code (#120)

Claude Code uses OSC 52 escape sequences for clipboard operations. The xterm.js ClipboardAddon was never loaded, so OSC 52 sequences were silently ignored.

3. Garbled rendering in Claude Code no-flicker mode (#121)

Claude Code's fullscreen/no-flicker mode (CLAUDE_CODE_NO_FLICKER=1) uses dense cursor-positioning sequences that get split when the PTY reader sends fixed 4096-byte chunks. Split escape sequences cause text fragments to render at wrong positions.

4. Garbled screen on reattach after alternate-screen apps (#122)

After exiting Claude Code (or vim/htop), reattaching to the session showed a screen full of repeated bash prompts. The output buffer contained raw alternate-screen escape sequences that produced garbage on replay.

5. Dependency cleanup

Replaced mlflow-tracing with mlflow-skinny==3.10.1 — the app only needs the lightweight MLflow client, not the tracing package.

6. Docs fix

Corrected repo name in deployment docs (coding-agents-in-databrickscoding-agents-databricks-apps).

Changes

File Change
app.py MAX_CONCURRENT_SESSIONS backend cap (env var, default 5) with TOCTOU-safe double-check; PTY read chunk 4096→65536 bytes
app.yaml MAX_CONCURRENT_SESSIONS env var
static/index.html Session creation prompt; createWriteBatcher() for output batching; ClipboardAddon; session count label in tab bar; alternate screen exit detection; SIGWINCH-based reattach; 429 error message; escape key handling
static/lib/addon-clipboard.js New: xterm.js ClipboardAddon for OSC 52
pyproject.toml Replaced mlflow-tracing with mlflow-skinny==3.10.1
requirements.txt Regenerated
docs/deployment.md Fixed repo name
README.md Fixed repo name
tests/test_session_limit.py New: 8 tests for session limit enforcement
tests/test_clipboard_addon.py New: 8 tests for clipboard addon integration

Test plan

Automated (all pass locally)

  • uv run python -m pytest tests/ -q — 163/163 pass (excluding 2 pre-existing flaky network tests in test_gateway_discovery.py and test_npm_version_pinning.py)

Manual testing on Databricks Apps

Session prompt (#118):

  • Fresh deploy, no sessions — page load creates session normally (no prompt)
  • Reload with 1+ sessions alive — shows prompt listing existing sessions
  • Select session number — reattaches (scrollback replayed)
  • Press n — creates new session
  • Press q or Esc — creates new session (cancel = default to new)
  • New tab / split pane with existing sessions — shows prompt
  • Session picker (Ctrl+Shift+S) — works as before, no double-prompt

Session cap (#118):

  • Create 5 sessions, try 6th — red error in terminal: "Maximum 5 concurrent sessions reached"
  • Error message includes hint to increase MAX_CONCURRENT_SESSIONS or use Ctrl+Shift+S

Session count label (tab bar):

  • Text label visible on right side of tab bar (e.g., "2 active sessions")
  • Increments when new session created
  • Decrements when session exits (typing exit)
  • Decrements when session killed via picker (dN)
  • Updates to "No active sessions" when all closed
  • Updates after kill-all ("x" in picker)
  • Updates after cancelled picker (q/Esc)

Clipboard / OSC 52 (#120):

  • Open Claude Code inside terminal session
  • Copy text from Claude Code output — works via OSC 52
  • Paste into Claude Code — works
  • Regular terminal copy-paste still works

Output rendering / no-flicker mode (#121):

  • Run CLAUDE_CODE_NO_FLICKER=1 claude inside a session
  • Ask Claude to run bash commands / edit files
  • Text renders correctly — no garbled fragments or mispositioned words
  • No longer need to resize window to fix rendering
  • Normal (non-no-flicker) mode still renders correctly
  • Rapid output (cat large file) renders smoothly
  • Exiting Claude Code no-flicker clears screen cleanly (150ms auto-clear)

Reattach after alternate-screen apps (#122):

  • Reattach to a session running Claude Code — UI redraws via forced SIGWINCH
  • Reattach to a plain bash session — clean prompt (no garbled replay)
  • No blank screen on reattach (SIGWINCH forces redraw without manual resize)

Dependencies:

  • pip list | grep mlflow shows only mlflow-skinny==3.10.1
  • App starts without import errors

Docs:

  • Deployment docs show correct repo URL (coding-agents-databricks-apps)

#118)

Prevent OOM crashes on 6GB Databricks Apps containers by reducing
unnecessary session accumulation. Users now see their active sessions
and can reuse one instead of blindly spawning a new one each time.

- Add session-aware prompt on page load, new tab, and split pane
- Add MAX_CONCURRENT_SESSIONS backend cap (env var, default 5)
- Add session count badge on the toolbar sessions button
- Add 8 tests covering the session limit enforcement
@datasciencemonkey datasciencemonkey self-assigned this Apr 14, 2026
@datasciencemonkey datasciencemonkey added the enhancement New feature or request label Apr 14, 2026
Claude Code uses OSC 52 escape sequences for clipboard operations.
Without the ClipboardAddon, xterm.js silently ignores these sequences,
making copy-paste inside Claude Code sessions non-functional.

- Add addon-clipboard.js to static/lib/
- Load and initialize ClipboardAddon in createPane()
- Add 8 tests verifying addon presence and integration
Align with the lockfile pin to prevent accidental upgrades that could
pull in incompatible transitive dependencies.
No Python code imports mlflow. Removing unused deps cuts the package
count from 65 to 50, reducing container image size and attack surface.
Keep only mlflow-skinny (lightweight client) — mlflow-tracing and the
full mlflow package are not needed. Pins to 3.10.1.
…ation

Claude Code's no-flicker mode uses dense cursor-positioning sequences
that get split when the PTY reader sends fixed-size chunks. This causes
garbled rendering (text fragments at wrong positions).

- Add requestAnimationFrame write batching (~16ms) for PTY output
- Increase PTY read chunk size from 4096 to 65536 bytes
- Only PTY stream is batched; direct UI writes (splash, picker) unaffected
@datasciencemonkey datasciencemonkey changed the title feat: prompt users to reuse sessions, add concurrent session cap feat: session prompt, clipboard fix, output batching, and dep cleanup Apr 14, 2026
After replaying the output buffer on reattach, send ESC[?1049l to
ensure the terminal is on the main screen buffer. Prevents garbled
display when reattaching after exiting Claude Code no-flicker mode
or other alternate-screen apps (vim, htop).
When Claude Code no-flicker mode (or vim/htop) exits the alternate
screen, the restored main buffer has stale content overlapping with
exit output. Detect ESC[?1049l in the output stream and clear the
screen after 150ms to let exit output finish, then show a clean prompt.
The output buffer contains raw escape sequences captured at different
terminal dimensions or from alternate-screen apps. Replaying them
produces garbled output. Clear the screen after replay to guarantee
a clean terminal, then resize triggers the shell to redraw its prompt.
…122)

Buffer replay produces garbled output from stale escape sequences and
conflicts with alternate-screen apps (Claude Code no-flicker mode).
Replace with term.reset() + resize — the resize sends SIGWINCH so the
running app (bash, Claude Code, vim) redraws its own UI correctly.
The kernel only sends SIGWINCH when the PTY size actually changes.
On reattach, if the terminal dimensions are unchanged, no SIGWINCH
fires and the app (Claude Code in no-flicker mode) never redraws.
Fix by sending a 1-column shrink then restoring the real size,
guaranteeing SIGWINCH regardless of current dimensions.
- Add q/Esc/Q cancel handler to promptExistingSessions so the Promise
  can't hang forever if user presses an unrecognized key
- Add cancel() method to write batcher to clear pending data and timers
- Call batchWrite.cancel() from cleanupPane to prevent writes to
  disposed terminals when altExitTimer fires after pane teardown
Backend:
- Fix TOCTOU race in session cap: authoritative len(sessions) check
  inside the same sessions_lock as dict insertion. Early approximate
  check still rejects before forking a PTY.
- Use len(sessions) instead of exited flag filter (exited is dead code —
  sessions are popped, never marked)

Tests:
- Fix env var test to patch the actual module constant
- Fix bare open() calls in clipboard tests (use context managers)
- Use re.search for more specific OSC 52 handler assertion
- Replace exited-flag tests with removal-based tests matching real behavior
When the backend returns 429 (max sessions), display a clear error
in the terminal with the message and a hint to increase
MAX_CONCURRENT_SESSIONS in app.yaml or close sessions via Ctrl+Shift+S.
coding-agents-in-databricks → coding-agents-databricks-apps
Add overflow:visible to sessions button and reposition badge to
top-right corner (-4px offset) so it sits above the button edge
instead of being clipped by the flex container.
The red badge on the hamburger button was clipped and invisible.
Replace with a plain text label on the right side of the tab bar
showing "1 active session" / "2 active sessions" / etc.
Add updateSessionBadge() to cleanupPane (covers all exit paths) and
after individual session kills in the session picker. Count now
decrements when sessions are closed, exited, or killed.
Traced all session create/close/exit/switch paths and found 2 gaps:
- Cancelled session picker (Esc/q) early-returns before badge update
- Kill-all ("x") in picker didn't update badge before creating new session

Both now call updateSessionBadge() at the right point.
When a session exits (user types "exit"), cleanupPane fetches the
session count immediately — but the backend hasn't finished removing
the session yet (3s SIGKILL wait). Add a 4s delayed retry so the
count reflects the removal after backend cleanup completes.
@datasciencemonkey
Copy link
Copy Markdown
Owner Author

All tests pass.

Session management, clipboard, rendering, and dependency improvements.
@datasciencemonkey datasciencemonkey merged commit 291f8fb into main Apr 14, 2026
1 check passed
@datasciencemonkey
Copy link
Copy Markdown
Owner Author

v0.17.0 Ship Summary

What shipped

  1. Session creation prompt — users see existing sessions before creating new ones, reducing OOM risk on 6GB containers
  2. MAX_CONCURRENT_SESSIONS cap — backend hard limit (env var, default 5) with TOCTOU-safe double-check under lock
  3. Session count label — tab bar shows "N active sessions" with updates on every create/close/exit/kill path, including a 4s delayed retry for backend cleanup lag
  4. ClipboardAddon (OSC 52) — copy-paste now works inside Claude Code sessions
  5. Write batching — requestAnimationFrame coalescing prevents escape sequence fragmentation in no-flicker mode
  6. Alternate screen exit auto-clear — 150ms debounced clear after vim/Claude Code exits
  7. SIGWINCH-based reattach — forced size toggle guarantees app redraw on session switch
  8. 429 error feedback — clear terminal message when session limit is hit
  9. Dependency cleanup — mlflow-tracing → mlflow-skinny 3.10.1
  10. Docs fix — corrected repo name in deployment docs

Test results

  • 163/163 automated tests pass (2 pre-existing flaky network tests excluded)
  • 16 new tests added (8 session limit + 8 clipboard addon)
  • 3 independent code reviews completed (frontend, backend, tests) — all critical/high issues resolved

Issues closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment