Skip to content

fix(routing): expire stale pending-text + name session from its first message#140

Merged
Time4Mind merged 2 commits into
mainfrom
fix/pending-text-leak-and-autoname
Jun 28, 2026
Merged

fix(routing): expire stale pending-text + name session from its first message#140
Time4Mind merged 2 commits into
mainfrom
fix/pending-text-leak-and-autoname

Conversation

@Time4Mind

Copy link
Copy Markdown
Owner

Problem

A message typed while no active session exists is stashed in context.user_data["_pending_text"] and forwarded once a session is created. user_data is in-memory and only a bot restart clears it — so a stash can survive for hours and then be injected into an unrelated session created much later.

Field incident (2026-06-28)

  • 01:05 — user sends «почему смартфон на 100%». Bot had just restarted and marked all windows lost, so _resolve_active_window found no active session → opened the directory browser and stashed the text.
  • 01:05–01:09 — the auto-restore path rebuilt the previous session and delivered the text there, but never popped _pending_text.
  • No further restart for ~9.5 h → the stale stash lived on in memory.
  • 10:34 — user creates a new session; _session_create forwards the leftover 01:05 text into a brand-new window, which then catches the user's unrelated «найди сессию про глотание / альфа страховка» message and gets auto-named medical insurance.

Forensics confirmed in bot.log: single Starting Telegram bot at 01:02, No active session: showing directory browser at 01:05:32, then Forwarding pending text … len=93 at 10:34:26 with no intervening re-stash.

Fix

  • stash_pending_text / take_pending_text in handlers/directory_browser.py (TTL PENDING_TEXT_TTL_S = 10 min). Consumption drops a stale stash and always clears the slot, so a leaked pending message can never re-fire. Legacy bare-string format tolerated for state in flight across deploy.
  • _session_create and window_picker now consume via take_pending_text (one clearing read) instead of get-then-pop.
  • _session_create seeds the auto-name from the forwarded pending text (the session's real first human message) rather than whatever inbound lands first. maybe_auto_name stays one-shot via its re-entrancy guard.

Tests

  • tests/test_pending_text_ttl.py — fresh round-trip, stale-drop, TTL boundary, custom max-age, no-expiry, legacy string, absent/empty, None-safe.
  • Full suite: 736 passed; ruff + pyright clean.

🤖 Generated with Claude Code

Time4Mind and others added 2 commits June 28, 2026 11:17
… message

A message typed while no active session exists is stashed in
``context.user_data["_pending_text"]`` and forwarded once a session is
created. ``user_data`` is in-memory and only a bot restart clears it, so a
stash could survive for hours and then be injected into an unrelated
session created much later.

Field incident (2026-06-28): a 01:05 "почему смартфон на 100%" message was
stashed (no active session after an overnight restart marked all windows
lost), delivered to the resumed session via the auto-restore path — which
never popped ``_pending_text`` — and then, with no further restart to clear
memory, the stale stash was re-forwarded at 10:34 into a brand-new window.
That window then caught the user's unrelated "найди сессию про глотание /
альфа страховка" message and got auto-named "medical insurance".

Fixes:
- ``stash_pending_text`` / ``take_pending_text`` (TTL ``PENDING_TEXT_TTL_S``
  = 10 min): consumption drops a stale stash and always clears the slot, so
  a leaked pending message can never re-fire. Tolerates the legacy
  bare-string format for state in flight across deploy.
- ``_session_create`` and ``window_picker`` consume via ``take_pending_text``
  (single clearing read) instead of get-then-pop.
- ``_session_create`` seeds the auto-name from the forwarded pending text
  (the session's actual first human message), so a session is named by its
  first message rather than whatever inbound lands first. ``maybe_auto_name``
  stays one-shot via its re-entrancy guard.

Adds tests/test_pending_text_ttl.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_text

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Time4Mind Time4Mind merged commit a5ee827 into main Jun 28, 2026
4 checks passed
@Time4Mind Time4Mind deleted the fix/pending-text-leak-and-autoname branch June 28, 2026 08:20
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