Area
apps/server
Problem or use case
The ProviderSessionReaper terminates agent processes after 30 min of inactivity (DEFAULT_INACTIVITY_THRESHOLD_MS = 1800*1e3, instantiated without options — no env var or settings override). "Inactivity" only counts user turns: an agent that ended its turn while a background shell command is still running (e.g. waiting for a deploy result) gets reaped, its background task dies with it, and the completion notification can never fire. On the next user message the session resumes via --resume with "No completion record was found for this background shell command from the previous session" — the promised follow-up is silently broken, and the user has to notice and re-prompt manually.
Proposed solution
Treat sessions with running background shell tasks (or pending task notifications) as active and exempt them from reaping — or deliver the task result by respawning the session when the task completes.
Why this matters
Anyone using t3code for longer-running work (deploys, builds, CI watches) hits this: the agent's "I'll report back when it's done" contract is silently broken by infrastructure, which erodes trust in unattended runs. Keeping the completion contract intact makes t3code viable for exactly the async workflows a server-hosted GUI is best at.
Smallest useful scope
Skip reaping for sessions whose provider process has at least one running background shell task. No config surface needed.
Alternatives considered
- Prompt-level rules ("always poll in-turn, never end a turn waiting on a background check") — advisory only, models don't reliably follow them.
- External workaround (what we run now): long-runners detached in tmux writing
.done markers + a systemd timer that sends push notifications and triggers follow-ups. Works, but reimplements what the session already promised.
- A configurable inactivity threshold (env var) would soften the issue but just moves the cliff.
Risks or tradeoffs
A hung background task could keep a session alive indefinitely — worth capping with a max task lifetime or a much longer hard ceiling.
Examples or references
Observed on t3code 0.0.28, self-hosted on Ubuntu 24.04; reaper log: provider.session.reaped, reason: 'inactivity_threshold'.
Area
apps/server
Problem or use case
The ProviderSessionReaper terminates agent processes after 30 min of inactivity (
DEFAULT_INACTIVITY_THRESHOLD_MS = 1800*1e3, instantiated without options — no env var or settings override). "Inactivity" only counts user turns: an agent that ended its turn while a background shell command is still running (e.g. waiting for a deploy result) gets reaped, its background task dies with it, and the completion notification can never fire. On the next user message the session resumes via--resumewith "No completion record was found for this background shell command from the previous session" — the promised follow-up is silently broken, and the user has to notice and re-prompt manually.Proposed solution
Treat sessions with running background shell tasks (or pending task notifications) as active and exempt them from reaping — or deliver the task result by respawning the session when the task completes.
Why this matters
Anyone using t3code for longer-running work (deploys, builds, CI watches) hits this: the agent's "I'll report back when it's done" contract is silently broken by infrastructure, which erodes trust in unattended runs. Keeping the completion contract intact makes t3code viable for exactly the async workflows a server-hosted GUI is best at.
Smallest useful scope
Skip reaping for sessions whose provider process has at least one running background shell task. No config surface needed.
Alternatives considered
.donemarkers + a systemd timer that sends push notifications and triggers follow-ups. Works, but reimplements what the session already promised.Risks or tradeoffs
A hung background task could keep a session alive indefinitely — worth capping with a max task lifetime or a much longer hard ceiling.
Examples or references
Observed on t3code 0.0.28, self-hosted on Ubuntu 24.04; reaper log:
provider.session.reaped, reason: 'inactivity_threshold'.