fix(remote): route remote repository reads and mutations through typed commands - #548
Merged
Merged
Conversation
…d commands Remote (SSH) repositories now send every workspace, file, and commit read or mutation the desktop UI performs through the typed remote command layer. Operations with no typed command fail with a structured "unsupported" error instead of running local Tauri commands against a remote path. - CreateCommit always sends an idempotency key, and its verify-before-retry check uses the pre-commit working-copy change id instead of matching the message, so repeated messages such as "wip" no longer read as applied. - RebaseWorkspace uses the repository default branch, SplitCommit honours the commit and message and refuses hunk selections, and MoveWorkspaceChanges keeps files and hunks. - New typed reads: WorkspaceDiff, CommitDiff, CommitFileDiff, SearchFiles. - Every flag the typed commands emit is declared in the CLI config, and every request round-trips through its CLI arguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MbYSPHBRmDYwPJpHG33Y4t
Ziinc
commented
Sep 27, 2026
Collaborator
Author
There was a problem hiding this comment.
The CreateCommit check at verification_for now keys on the pre-commit working-copy change id, which retry_after_reconnect reads before it sends the mutation.
jj_commitrewrites@in place, so the change id survives and becomes the committed change. That tells "this commit landed" apart from "an older commit happens to say wip".- The marker is stored in the request itself, so the VM's idempotency record and its stale-claim recovery use the same check.
- The fidelity fixes (rebase default branch, split guard) refuse unsupported inputs instead of widening them, because a silent widening changes what the user committed.
Generated by Claude Code
Collaborator
Author
There was a problem hiding this comment.
The remote VM parses argv with this CLI config, and it rejects undeclared flags.
commits --path(split hunks) andconflicts --target/--value/--idempotency-keywere emitted bycli_argsbut never declared, so those commands could only fail over SSH.- The new
every_remote_cli_flag_is_declared_in_tauri_configtest incli/tests.rskeeps the two in sync from now on.
Generated by Claude Code
Collaborator
Author
There was a problem hiding this comment.
remoteMutation replaces dispatchMutation(repo.endpoint, …), which fell back to a local dispatch when the endpoint was null.
- It uses
dispatchMutationOverSsh(transport.endpoint, …)for SSH repos, matching the shape refactor(remote): remove Sprite-exec managed transport #543 leaves behind. assertLocalOperationis the safety net for operations with no typed command: it raises a structuredunsupported:error instead of running a local command against a remote path.resolveRemoteLocationlets the existing workspace-path APIs (jjSplit,jjRestoreFile,readFile) find the workspace id without changing their call sites.
Generated by Claude Code
Collaborator
Author
There was a problem hiding this comment.
The file-browser draft review (loadFileBrowserReview) is kept in memory for remote repos.
- The local command would open or create
.treq/local.dbunder a path that exists only on the remote host. - No typed command exposes that table yet. Keeping the draft for the session keeps commenting usable without writing to the wrong machine.
Generated by Claude Code
Resolve repository-adapter conflicts with the Sprite transport removal (#543): keep the SSH-narrowed activeForPath, drop the managed-Sprite import, and keep main's transportCreateCommit tests with the idempotency key the PR now sends. Refuse SplitCommit hunk selections before the idempotency store opens the repo, so the refusal does not depend on the repo path being writable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PGXpwptLRetsjFcHkL8x5
This branch was successfully deployed
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.
Summary
Covers remote-development PRD acceptance criteria 3 (normal review UI), 4 (core mutations over the typed command layer) and 7 (safe reconnect).
unsupported:error. They no longer run a local Tauri command against a path that exists only on the remote host.CreateCommit. Before, TS sent noidempotency_key, and the Rust enum requires one, so the request failed at the IPC boundary. The verify-before-retry check also treated any commit with the same message as already applied. That gave false positives on repeated messages such as "wip".RebaseWorkspaceused a hardcoded"main".SplitCommitignoredcommit, widened hunks to whole files and dropped the message.MoveWorkspaceChangesdropped files and hunks.WorkspaceDiff(the remote review diff now includes committed files),CommitDiff,CommitFileDiffandSearchFiles.Changes
Rust (
core/remote.rs,cli/mod.rs,tauri.conf.json)CreateCommithas a new optionalbase_change_id: the working-copy change id before the commit.retry_after_reconnectfills it with aWorkspaceChangeMarkerread before it sends the mutation.WorkspaceChangeMarkernow also returnsworking_copy_change_id. The verification check has three outcomes:DeleteWorkspaceverification treated every workspace as gone, because it compared numeric ids as strings.RenameWorkspaceverification read a field that does not exist, so it was always ambiguous. Both are fixed.RebaseWorkspacenow usesjj::get_default_branch.SplitCommitpasses the message through. It refuses a commit other than@and refuses hunk selections, so neither is silently widened.MoveWorkspaceChangesnow forwardsfilesandhunks.CreateWorkspacecarries the localmetadataJSON (title, description, moved files, sparse patterns).UpdateWorkspacecarriestitle.KIND_NAMESgoes from 40 to 44.commitsdid not declare--path, andconflictsdid not declare--target,--valueor--idempotency-key. The VM-side parser rejects undeclared flags, so remote split with hunks and remote conflict resolve were broken over SSH. Those flags are now declared, along with the new flags.TypeScript
repository-adapter.ts:remoteMutationalways usesdispatchMutationOverSsh(transport.endpoint, …)for SSH repos. It does not add callers ofdispatchMutation(compatible with refactor(remote): remove Sprite-exec managed transport #543).newIdempotencyKeygenerates one key per user action.assertLocalOperation/RemoteOperationUnsupportedErrorguard local-only operations.resolveRemoteLocationmaps a workspace path to its workspace id.repository-adapter-mutations.ts: create/delete/move/rename/push/update/retarget workspace, restore file, split, resolve, move/abandon/describe commit, and get commit description.api.ts/api-extra.tscall these adapters. Operations with no typed command are guarded:.treq/local.dbunder the remote path.remote-capabilities.tsaddsmergeWorkspace, marked unsupported for remote with a reason. The Dashboard merge-preview entry point checks it, andRemoteCapabilityNoticelists it. This is a small UI change: remote repos show one more line in the capability notice.remote-dispatch.ts: removes the duplicateCreateCommitunion member that madeidempotency_keyoptional, and adds the new fields and kinds.Testing
cargo test --lib --features tauri-test -- remote cli::tests: 206 passed, 3 failed. The 3 failures also happen without this change and come from this container:change_marker_…andbookmark_track_…need ajjbinary on PATH.skips_project_skills_when_cwd_is_not_writablefails because the tests run as root.@commit is refused; hunk selections are refused.maindefault branch. This test fails with the old hardcoded"main".tauri.conf.json, and every request round-trips throughcli_args→parse_remote_command_request.cargo clippy --locked --all-targets --all-features -- -D warnings: clean.cargo fmt --check: clean.npx vitest run --config vitest.unit.config.ts: 137 files, 694 tests, all passed. This includes the newsrc/lib/repository-adapter.test.ts, which checks:invokeinvokenpx tsc --noEmit, eslint and oxlint on the touched files, andbiome format: clean. The remaining eslint warnings inDashboard.tsxandapi-extra.tswere already there.test/integration/remote-workspace-ui.test.tsxopens a saved remote repo with no endpoint, which uses the local transport, so these paths are not exercised there. Its capability-notice assertions use substring matches.Not in this PR
jj_get_commits_ahead), check-and-rebase, home-branch rebase, restore-all/snapshot, pull, switch branch, archive/schedule, bookmark-conflict resolution, undo/revert, shifting timestamps and the resolve-conflicts session. These now fail with anunsupported:error rather than running locally. Only merge is disabled up front in the UI through capabilities.list_directory,ls_workspace_with_status) is still local-only.transport: local, so its calls still go to local commands. The integration tests depend on this behaviour, and fixing it belongs with the transport cleanup.set_workspace_target_branchTauri command also hardcodes"main". Only the remote path is fixed here.🤖 Generated with Claude Code
https://claude.ai/code/session_01MbYSPHBRmDYwPJpHG33Y4t
Generated by Claude Code