Repository navigation
Delete a run/session completely #23
Description
Activity
Plan
I have everything needed to write the plan.
Delete a run/session completely, with confirmation + stop-if-running
Context
Issue #23 asks for two related controls in the web console: (1) a per-run "Delete" action that removes a run and all trace of it from the database, gated behind a confirmation dialog, and (2) a per-run "Stop" action for runs currently in flight. Deleting a currently-running run must stop it first, then delete it. Today the console can only
Cancelan in-flight run (which marks itcanceledin the DB but keeps the row/history forever); there is no delete path anywhere in the code (grepforDeleteRun/DELETE FROM runsfinds nothing) and no confirmation-dialog component exists in the frontend at all.Backend changes
internal/store/store.go— add aDeleteRunmethod next to the other run CRUD (afterFailRun/GetRun, ~line 588-634), following the exact pattern already used byReleaseClaim(line 379-386) andClearGate(line 892):// DeleteRun permanently removes a run and every trace of it: its events, // its sessions row, and its on-disk transcript. There is no undo. func (s *Store) DeleteRun(ctx context.Context, runID string) error { tx, err := s.db.BeginTx(ctx, nil) ... tx.ExecContext(ctx, `DELETE FROM events WHERE run_id = ?`, runID) tx.ExecContext(ctx, `DELETE FROM sessions WHERE run_id = ?`, runID) res, err := tx.ExecContext(ctx, `DELETE FROM runs WHERE id = ?`, runID) // if rows affected == 0, return ErrNotFound tx.Commit() }
Since there are no declared foreign keys between
runs,events, andsessions(confirmed —PRAGMA foreign_keys(1)is set at line 170 but nothing is declared to enforce), cleanup ofeventsandsessionsrows must be done manually inside a transaction, not relied upon via cascade. Returnstore.ErrNotFound(the existing sentinel, line 601) if therunsdelete affected 0 rows, matchingGetRun's behavior.The transcript file itself (
run.LogPath, e.g.internal/orchestrator/loop.go:454) should also be removed. Do this in the server handler, not the store, since the store package doesn't currently reach into the workspace/logs filesystem beyond what's already passed in — fetch the run first (GetRun), delete the DB rows, thenos.Remove(run.LogPath)best-effort (ignoreos.IsNotExist).internal/server/server.go:- Add
deleteRunhandler nearcancelRun(line 410-421):- Fetch the run via
s.store.GetRun— 404 vias.failiferrors.Is(err, store.ErrNotFound). - If
!store.IsTerminal(run.Status)(i.e., it's in flight), calls.ctrl.Cancel(id)first.Cancelis async (it only cancels the context; the orchestrator's own goroutine doesFailRunmoments later — see loop.go 150-159, 1065), so don't require it to succeed or block waiting for the terminal state — the delete should proceed regardless, since deleting the row doesn't depend on what status it eventually lands on. Ifs.ctrlis nil (no controller attached, e.g. in some deployment modes) just skip straight to delete. - Call
s.store.DeleteRun(c.Context(), id), then best-effortos.Remove(run.LogPath). - Return
c.JSON(fiber.Map{"deleted": true, "run": id}).
- Fetch the run via
- Register the route in
routes()(line 94-122). Following house style — every mutation in this codebase isPOST, never aDELETEverb (/pause,/resume,/runs/:id/cancelare all POST) — add:(Design decision, flagged for the reviewer: a reals.app.Post("/runs/:id/delete", s.deleteRun)
DELETE /runs/:idwould be more RESTful, butPOST .../deletematches the zero precedent-breaking convention already established for/runs/:id/cancel. Recommend sticking withPOST /runs/:id/deleteunless the reviewer prefers introducing theDELETEverb.) - No new
Controllerinterface method is needed —Cancel(already in the interface, line 37) is sufficient.
Tests —
internal/server/server_test.go: addTestDeleteRunfollowingTestCancelRun(line 221-236)/TestRunsEndpoints(line 154-206) shape: seed a run withst.CreateRun, POST to/runs/:id/delete, assert 200 and thatst.GetRunnow returnsErrNotFound. Add a second case for deleting an in-flight run: seed a run with a non-terminal status, usefakeController(line 22-40) to assertCancelwas called with the right run ID before/alongside the delete.internal/store/store_test.go: addTestDeleteRuncalling the store method directly, seeding an event and a session row for the samerun_idand asserting all three are gone afterward, plus a not-found case.Frontend changes (
internal/web/assets/app.js)Confirmation dialog — none exists in this codebase (confirmed: no
<dialog>, no modal CSS, nowindow.confirmanywhere). Given the project's explicit "no build step, no dependencies" style (see file's own header comment, line 1-3), use the simplest option consistent with that:window.confirm("Delete this run permanently? This cannot be undone.")before calling the delete API. This avoids adding a whole modal component/CSS for a single use case. (Flagging as a judgment call — a custom<dialog>-based confirm would look nicer and is reusable, but is meaningfully more code for a one-off; recommend the native confirm unless the reviewer wants the nicer UX enough to justify building a modal component now.)Runs list page —
runsTableBody(line 647-668) and the header row inrenderRuns(line 634-641) currently have no actions column. Add an "Actions" header and, per row, a container with:- A "Stop" button, shown only when
!IsTerminal-equivalent(mirror the terminal-status list already used for the status filter dropdown, line 605, or reuse the four in-flight status stringsclaimed/working/verifying/pushed), callingapi.post('/runs/:id/cancel')— same call the dashboard'sinFlightTablecancel button makes (line 552-561). - A "Delete" button (
class: "btn btn-danger", matchingcancelBtn's class at line 552) that runs thewindow.confirmabove, thenapi.post('/runs/:id/delete'), then re-renders the table (callrenderRuns(false)the same wayapplyBtn's handler does at line 624, or for the dashboardinFlightTablecase callrefreshStatus()ascancelBtndoes at line 557).
Follow the exact button-disable-during-request pattern already used at lines 553-561 (btn.disabled = truein atry/finally).
Run detail page —
renderRunDetail(line 672-766) currently has zero action buttons. Add a small button row near the top (after the<h1>at line 685) with the same Stop/Delete buttons, conditioned onrun.Statusfor Stop, and navigating back to#/runs(window.location.hash = "#/runs") after a successful delete, since the detail page for a deleted run no longer has anything to show.Dashboard in-flight table (
inFlightTable, line 538-575) — this table only ever shows in-flight runs, so every row is stoppable by definition; no change needed to add a "Stop" label distinct from today's "Cancel" — issue #23's "stop" and the existing "Cancel" are the same operation. Optionally add a Delete button next to the existing Cancel button here too for convenience, reusing the same stop-then-delete client-side sequence (call cancel, then delete) — but this is optional since the Runs list/detail pages are the primary places "delete a run" naturally lives; flagging as a nice-to-have, not required by the issue.CSS (
internal/web/assets/app.css) — no new classes strictly required if reusing.btn/.btn-danger(lines 155-179) for the new buttons; add a minor.row-actions { display: flex; gap: 6px; }wrapper style if the actions cell needs multiple buttons side by side.Verification
go test ./internal/store/... ./internal/server/...— covers the newDeleteRunstore method and the new/runs/:id/deletehandler including the stop-then-delete path viafakeController.- Manual UI check:
go run ./cmd/agent.go(or however the daemon's normal dev entrypoint is invoked) withserver.ui: true, open the console, start/observe an in-flight run, click Delete on a running run and confirm it disappears from both the dashboard and the runs list, and that aGET /runs/:idafterward 404s. Also verify a terminal (already-finished) run can be deleted without hitting the cancel path, and that its transcript file is removed from the logs directory. - Confirm the confirmation dialog actually blocks deletion when "Cancel" is clicked in the native
confirm()popup.
Open questions for the reviewer
- Route verb:
POST /runs/:id/delete(matches house convention) vs. a trueDELETE /runs/:id— recommend the former unless told otherwise. - Confirmation UX: native
window.confirm()vs. a custom modal — recommend the former for minimal footprint given zero existing modal infrastructure. - Should deleting a run also delete its
sessionstable row(s)? Recommend yes (a session row is meaningless without its parent run), and the plan above does this viaDELETE FROM sessions WHERE run_id = ?inside the same transaction. - Whether to add Delete to the dashboard's in-flight table too, or only to the Runs list/detail pages — recommend the latter as sufficient to satisfy the issue, with the former as optional polish.
Reply with exactly
implementto approve this plan and start the change. Reply with anything else and the plan will be revised to address it.coding-agent-loop run
ad78deb9-d9ee-485e-b8ea-01a366f7ad6e, modelclaude-sonnet-5, cost $0.7201- Add
- addedagent-plannedManaged by coding-agent-loopManaged by coding-agent-loopand removed
on Sep 6, 2026 implement
Opened a draft pull request for this issue: #25
Tests failed (
make test) — see the PR for output.Comment
implementagain if you want another attempt at this issue.coding-agent-loop run
f858df05-637d-4f9b-8dbf-c9200f2c67aa- added and removedagent-plannedManaged by coding-agent-loopManaged by coding-agent-loop
on Sep 6, 2026
From the UI, give the user the option to completely delete the history of a run/session completely. There should no longer be a reference or history of it in the database. Add a confirmation dialog before completely deleting it. Also, add a button to "stop" a specific run/session if its currently running. If the user wants to delete a running session, then it should be stopped first then deleted.