Skip to content

feat(environments): archive grace period + Restore environment#242

Draft
ymichael wants to merge 1 commit into
mainfrom
bb/handle-missing-environment-cases-thr_m4iqerqqi9
Draft

feat(environments): archive grace period + Restore environment#242
ymichael wants to merge 1 commit into
mainfrom
bb/handle-missing-environment-cases-thr_m4iqerqqi9

Conversation

@ymichael

@ymichael ymichael commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Problem

Two gaps around a thread losing its environment:

  1. Accidental archive bricks a thread. Archiving the last live thread in a managed environment immediately tore down its git worktree — no undo, uncommitted work gone.
  2. No way back from a gone environment. A thread whose environment is destroying/destroyed showed a dead-end "Environment is no longer available" banner.

What this does

Archive grace period (the brick fix). Archiving the last live thread now leaves the environment retiring for a configurable window (managedEnvironmentRetireGraceMs, default 10s) before the worktree is destroyed. The gate lives in advanceEnvironmentCleanup and is durable across restart (keyed on the row's updatedAt; no in-memory timer). It only applies when a revivable archived thread remains, so a deleted thread's orphaned environment is still cleaned up immediately.

Lossless undo. Within the window:

  • Un-archiving the thread fires the existing retire.cancelled → environment back to ready, worktree (and uncommitted work) intact.
  • A 10s "Thread archived — Undo" toast offers the same one click. The durable banner Unarchive remains as a fallback.

Restore environment. POST /threads/:id/restore-environment reprovisions a fresh environment for a thread whose environment is gone:

  • The daemon's createWorktree now checks out an existing branch in place instead of -B-resetting it, so committed work (which survives git worktree remove) is recovered.
  • The thread is re-seeded into idle (no automatic turn); routing handles retiring (revive in place), destroying (409, retry), unmanaged (409), and pruned envs (409).
  • A "Restore environment" action in the read-only banner ("Cleaning up…" while destroying, active once destroyed).

T3-style per-turn checkpointing was evaluated and left out of scope (rationale in the plan): the grace period makes accidental archive lossless without new storage, and it conflicts with T3's own delete-refs-on-archive hygiene.

Design notes and exit criteria: plans/environment-restore-and-archive-grace-period.md.

Testing

  • Typecheck clean across server, db, host-workspace, server-contract, app, integration-tests.
  • Unit/integration: server 692, db 305, host-workspace 163, app 761 — all pass.
  • New regression coverage: grace-window defer-then-destroy, deleted-thread immediate cleanup, unarchive revive, banner Restore action (enabled vs "Cleaning up…"), and an end-to-end integration test that commits work → archives (destroy) → restores → asserts the committed file reappears in the fresh worktree on the same branch.

Follow-ups (not blocking)

  • Optional: hide the Restore button for unmanaged environments in the UI (the server already returns 409).

Not needed: SideChatTabContent's "no longer available" copy is a not-found thread label, not an environment-gone surface — there is nothing to restore there.

🤖 Generated with Claude Code

Archiving the last live thread in a managed environment no longer destroys
its worktree immediately. The environment stays `retiring` for a configurable
grace window (default 10s via MANAGED_ENVIRONMENT_RETIRE_GRACE_MS), during which:
- un-archiving revives it in place (retire.cancelled) — a lossless undo that
  preserves the worktree and uncommitted work, and
- an "Undo" toast on archive offers the same one click.

After the window the worktree is reclaimed by the existing recovery sweep (the
grace gate lives in advanceEnvironmentCleanup and is durable across restart via
the row's updatedAt clock; no in-memory timer). The gate applies only when a
revivable archived thread remains, so a deleted thread's orphaned environment is
still cleaned up immediately.

A new "Restore environment" action (POST /threads/:id/restore-environment)
reprovisions a fresh environment for a thread whose environment is gone. The
daemon's createWorktree now checks out an existing branch in place instead of
`-B`-resetting it, so committed work (which survives `git worktree remove`) is
recovered; the thread is re-seeded into idle without an automatic turn.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ymichael ymichael force-pushed the bb/handle-missing-environment-cases-thr_m4iqerqqi9 branch from 52cc265 to 0943c24 Compare June 18, 2026 23:36
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