feat(client): send command ids and write preconditions from every domain client
Summary
- The terminal, file, git, worktree, workspace and install clients did not send a command id. A resend after a lost reply could run the action twice, and a file save could overwrite a newer file without a warning.
- This PR makes every client of those areas send a command id. File saves and git commits also send what they expect to replace, so a stale write is refused.
- The host now refuses an action that must have a command id when the id is missing.
- This is the last of four pull requests. Pull requests 1088, 1091 and 1092 are the host and client engine parts.
Related Issue
Closes #1068
I searched open and closed pull requests. No other pull request covers this work. It completes the stack of 1088, 1091 and 1092.
Type of Change
- feat: new feature
- fix: bug fix
- docs: documentation only
- style: formatting or non-functional styling changes
- refactor: internal cleanup with no behavior change
- perf: performance improvement
- test: adds or updates tests
- build: build or dependency changes
- ci: CI/CD workflow changes
- chore: maintenance or tooling
- revert: reverts a previous change
What Changed
- The web HTTP client adds an
X-Command-Idheader to each action that changes state. The terminal socket client sends a command id for spawn, write, kill, rotate and revoke. - The desktop terminal, git, worktree, workspace and install commands pass a command id to the host.
- The editor sends the hash of the file it read as the expected content on save. If the file changed, the editor shows a conflict with an Overwrite action. After an unconfirmed save the editor drops the old hash.
- The git status store remembers the head commit and sends it as the expected head on commit.
- The host refuses these actions with
invalid_command_id(orINVALID_COMMAND_ID) when the id is missing. Reads, idempotent actions and prompts keep an optional id. - A write that the host cannot confirm after a long delay is reported as an unknown outcome. It is not run again.
- Tests and e2e specs now send command ids. The changelog has one entry for the feature.
How It Was Tested
-
bun run ci(Biome lint + format + imports, strict mode) -
bun run typecheck -
bun run test -
cargo clippy --all-targets -- -D warnings(in src-tauri) -
cargo test(in src-tauri) - Manual verification completed
- Not applicable
I also ran cargo clippy --bin termul-server --features standalone-server -- -D warnings. New tests cover a resend with the same id for each action, a missing id, a stale file hash, a stale expected head and the desktop error mapping.
Screenshots or Recordings
No new screen. The editor conflict now has an Overwrite action. It is covered by store tests, not by a screenshot.
CI & Review Gate
- All CI checks pass (PR Validation, Rust Checks, Build Verification, Security Scans)
- Code review comments from CodeRabbit / Claude have been addressed or resolved
- No unresolved review findings remain
Checklist
- My PR title follows the conventional commit format used by this repo
- I linked the related issue or explained why none exists
- I updated docs when needed
- I added or updated tests when needed
- I verified the change does not introduce unrelated modifications
- I read AGENTS.md and followed the contributor guidelines
- A human reviewed the complete diff before submission
Summary by cubic
Every domain client now sends a command id on mutations, so a resend after a lost reply never runs the action twice, and the host refuses ledgered commands that lack one. File saves and git commits carry preconditions so a stale write is refused instead of silently overwriting newer content.
Behavior
- All terminal, file, git, worktree, workspace, and install clients mint a
commandId(desktop arg, HTTPX-Command-Id, or socketcommandId) and resend an uncertain action under the same id, keyed by the full request so changed content gets a fresh id. - A save sends the hash of the file as read and a commit its expected head; the host answers a mismatch with
PRECONDITION_FAILED, and a commit with no readable head fails closed rather than committing with no precondition. When HEAD cannot be read, the commit footer still renders and no precondition is sent. - New terminal input queues behind buffered input that has not yet been confirmed, and a save whose outcome the host cannot confirm is reconciled with the user; a refused save shows the "file changed externally" conflict with an Overwrite action, and auto-save stops retrying that file.
- The host now rejects a ledgered action without a command id (
INVALID_COMMAND_ID/invalid_command_id) before running it; reads and idempotent actions still need none.
Summary by CodeRabbit
-
New Features
- Added safeguards against overwriting files changed elsewhere. You can reload the latest version or choose to overwrite it.
- Git commits are now checked against the current branch state, and file writes can be checked against the expected file version.
- Added more reliable handling for repeated or interrupted terminal, Git, workspace, and file operations.
-
Bug Fixes
- Auto-save now avoids files with external changes and suppresses unnecessary retry notifications after conflicts.