Skip to content

v0.21.14.0 fix(rm): enrich workspace-not-found diagnostic + pin remote dispatch to row's project#66

Merged
avinashjoshi merged 2 commits into
mainfrom
improve-remote-rm-error-messaging
May 28, 2026
Merged

v0.21.14.0 fix(rm): enrich workspace-not-found diagnostic + pin remote dispatch to row's project#66
avinashjoshi merged 2 commits into
mainfrom
improve-remote-rm-error-messaging

Conversation

@avinashjoshi

@avinashjoshi avinashjoshi commented May 28, 2026

Copy link
Copy Markdown
Owner

Summary

Two fixes that compose to address "remote canopy rm is opaque when it fails."

v0.21.13.0 — remote-side: enrich the not-found error. cmd/canopy/rm.go gets rmEnrichNotFound, replacing the bare workspace.Find(X): workspace: not found with: the project the lookup ran against, the workspaces that ARE registered there, a cross-project hint when a same-named workspace exists under another project root, and a pointer at --force as the silent-success escape hatch. The --force shortcut in rmHandleFindErr still wins, so the TUI's force-delete-on-stale-row path stays quiet. Best-effort: mgr.List / mgr.Store.Load failures degrade to the shorter form.

v0.21.14.0 — local-side: pin the remote dispatch to the row's project. When the TUI dispatched a remote canopy rm or retry for a row on tower, it ran the verb from the laptop's cwd. If that cwd wasn't inside a registered project, cmd/canopy/host_resolve.go's resolveOnForSwitch fell back to "first project on host" and rendered the dispatch source as registry:tower/<project> (fallback). Benign on a one-project host; a real correctness risk on a multi-project host (the rm could land in the wrong project). internal/ui/update_remote.go gains remoteCwdArg, returning [\"--remote-cwd\", <path>] when the in-memory host registry knows the path and nil otherwise. update_delete.go and update_retry.go both thread it through execRemoteVerb.

These compose: the dispatcher lands the verb in the right project (v0.21.14.0), and when the workspace still isn't there, the remote canopy explains why (v0.21.13.0).

Most useful through the SSH dispatch on a remote host. Before:

Dispatching to cassy@tower (registry:tower/canopy (fallback)):
exec canopy rm noble-lichen --yes
Error: workspace.Find(noble-lichen): workspace: not found
Error: remote canopy rm failed: exit status 1

After:

Dispatching to cassy@tower (registry:tower/canopy):
exec canopy rm noble-lichen --yes --remote-cwd /home/cassy/Work/canopy
Error: workspace \"noble-lichen\" not found in project \"canopy\" (/home/cassy/Work/canopy).
Workspaces here: alpha-fox, bravo-jay.
If the TUI showed this workspace, its list may be stale — refresh the host and try again, or re-run with --force to make a missing workspace a silent success.
Error: remote canopy rm failed: exit status 1

Test Coverage

cmd/canopy/rm_test.go:

  • TestRmEnrichNotFound_ListsSiblings — siblings listed; project name, root, --force, and stale-list hints all surface.
  • TestRmEnrichNotFound_EmptyProject — fresh project; distinct "no workspaces registered" line.
  • TestRmEnrichNotFound_CrossProjectHint — same-named workspace under another project root is surfaced.
  • TestRmEnrichNotFound_NoCrossProjectFalsePositive — no cross-project hint when no other project has a same-named workspace.

internal/ui/model_test.go:

  • TestRemoteCwdArg_PinsKnownProject — when the host registry knows the (host, project) path, remoteCwdArg returns [\"--remote-cwd\", <path>].
  • TestRemoteCwdArg_UnknownProjectReturnsNil — when the registry doesn't know the path (unknown host or unknown project), returns nil so dispatch stays in its legacy shape. Both branches pinned.

Existing rmHandleFindErr / remoteCwdForRow tests still pass. go test ./... clean.

Pre-Landing Review

Self-review on the diff:

  • No SQL / LLM / migration / API contract surface.
  • Error path degrades gracefully (listErr/sErr ignored; original not-found still surfaced).
  • remoteCwdArg returns nil on unknown paths instead of guessing — guessed paths would trip buildRemoteScript's exit-7 cwd pre-check and produce a worse diagnostic than the legacy fallback.
  • findDeleteTargetRemoteHost signature change: callers updated (one in update_delete.go, one in view.go).
  • No new dependencies.

Test plan

  • go test ./... passes locally
  • All 9 rm-flow tests (3 existing + 4 new in cmd/canopy + 2 new in internal/ui) pass uncached
  • Manual: trigger remote rm against a non-existent workspace from a TUI launched outside any project (canopy from ~) and confirm:
    • Dispatch source line drops (fallback) annotation
    • Dispatched command includes --remote-cwd /path/to/project
    • Remote canopy v0.21.13.0+ shows the enriched diagnostic instead of the terse one

Deployment note

The v0.21.13.0 piece changes the remote canopy's error message — it only takes effect once the remote host upgrades to v0.21.13.0+. The v0.21.14.0 piece is purely local — it ships in the laptop binary and takes effect immediately. So once this PR lands and the laptop upgrades, the dispatch will already pin --remote-cwd and drop (fallback); the richer remote error follows after canopy upgrade --on <host>.

🤖 Generated with Claude Code

avinashjoshi and others added 2 commits May 28, 2026 16:10
`canopy rm <name>` (most painfully through the SSH dispatch on a remote
host) used to fail with the bare `workspace.Find(X): workspace: not
found`, leaving no signal about whether the cause was a typo, a stale
TUI row, or the wrong project on the remote. `rmEnrichNotFound` replaces
that line with: the project the lookup ran against, the workspaces that
ARE registered there, a cross-project hint when a same-named workspace
exists under another project root, and a pointer at --force as the
silent-success escape hatch. Best-effort: store/list failures degrade to
the shorter form rather than masking the underlying not-found signal.
`--force`'s rmHandleFindErr shortcut still wins, so the TUI's
force-delete-on-stale-row path stays quiet. Tests cover siblings listed,
empty project, cross-project hint present, and no false-positive
cross-project hint.

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

When the TUI dispatched a remote `canopy rm`/`retry` for a row on tower,
it ran the verb from the laptop user's cwd. If that cwd wasn't inside a
registered project (the bare-`canopy`-from-`~` case), cmd/canopy's
`resolveOnForSwitch` fell back to "first project on host" and rendered
the dispatch source as `registry:tower/<project> (fallback)`. Benign on
a one-project host, silently-wrong on a multi-project host.

`internal/ui/update_remote.go` gains a `remoteCwdArg` helper that
returns `["--remote-cwd", <path>]` when the in-memory host registry
(`m.hostList[].Projects` — populated from `~/.canopy/hosts.json`) knows
the path for (host, project), and `nil` otherwise. `update_delete.go`
and `update_retry.go` thread it through `execRemoteVerb`. When the
registry doesn't know the path we stay in the legacy no-flag shape on
purpose — a guessed path that doesn't exist on the remote would trip
`buildRemoteScript`'s exit-7 cwd pre-check and degrade the diagnostic
further. `findDeleteTargetRemoteHost` now also returns the row's
Project so the (host, project) lookup happens at the dispatch site
without re-walking `m.filteredRows`. Composes with v0.21.13.0's
enriched not-found error: this lands the verb in the right project,
and when the workspace still isn't there the remote canopy explains
why.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avinashjoshi avinashjoshi changed the title v0.21.13.0 fix(rm): enrich workspace-not-found diagnostic v0.21.14.0 fix(rm): enrich workspace-not-found diagnostic + pin remote dispatch to row's project May 28, 2026
@avinashjoshi avinashjoshi merged commit 9d69e2b into main May 28, 2026
2 checks passed
@avinashjoshi avinashjoshi deleted the improve-remote-rm-error-messaging branch May 28, 2026 23:28
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