Add ephemeral workspaces - #42
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #37.
Create-from-issue already landed in #35, so this adds the second half: a workspace created from an issue can be marked ephemeral, and the bridge deletes it once the work it was opened for is finished.
How it works
POST /workspacestakesephemeral: truealongsideissueNumber(400 without one). The bridge writes a record to a newephemeral.json—{repoName, name, ship, issueNumber, branch, cleanup, blockedReason, blockedAt, pullRequest}— and annotates every workspace it serves with that block, so it rides the existing/eventssocket into the client. Ships are unchanged and know nothing about it.A sweep runs every 5 minutes (
sweepIntervalMs,0disables) plus once at startup, withPOST /workspaces/sweepto force a pass and see what it did. Per record it asks a newRepoProvider.pullRequestsForBranch— needed because the existinglistPullRequestsis unpaginated and cannot see closed PRs past GitHub's first 30 — and cleans up when the branch's PRs are all closed, or when there are none and the issue itself is closed. The branch is the one linked at create time and is pinned: switching branches later neither re-points nor cancels the watch.It will not delete work you can't get back
Cleanup goes through a new
?force=falseon the ship's delete, which refuses with 409 when the clone has a dirty tree (untracked included), commits absent from every remote on any local branch, or a stash. Every existing caller keeps today's unconditional behaviour.A refusal is not silent: the record turns
blockedwith the ship's own explanation, shows up wherever the workspace is listed (⚠ EPHEMERAL — issue #41 · PR #118 closed · cleanup blocked: 2 commits not on any remote), and is retried each pass until someone pushes or deletes by hand.Nothing is written to the forge — no branch deletion, no comments.
Record lifecycle
Dropped when the repo is unregistered, the ship is removed, or the workspace is deleted. A vanished workspace is only forgotten once its ship is online to say so, so a rebooting ship never quietly disarms the watch. An offline ship or a provider that errors leaves the whole repo's records untouched for that pass rather than reading as "no pull request".
Verification
bun test925 pass / 0 fail andbun run typecheckclean — checked out and re-run at each of the 8 commits individually, not just the tip. New coverage: real-git tests for every refusal case on the ship (including a commit on a branch that is not checked out), the full sweep decision table against a fake provider and fake ships, store round-trips, and the client's form and badge logic. No test waits on the timer; the timer gets one test that it fires and does not overlap.🤖 Generated with Claude Code