Skip to content

feat(cli): make commands safe to rerun with --request-id - #426

Open
PiquelChips wants to merge 1 commit into
mainfrom
feat/cli-request-id
Open

PiquelChips wants to merge 1 commit into
mainfrom
feat/cli-request-id

Conversation

@PiquelChips

@PiquelChips PiquelChips commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

When a command lost its connection mid-way, there was no safe way to run it again: each run made up a fresh idempotency key, so a rerun could repeat a change the daemon had already accepted. Reusing a key didn't help either, because the daemon's replay fingerprint included the preconditions the CLI inspects, which the first run had just changed, so a rerun got 409 request_id_conflict.

  • --request-id ID (CLI-10, D9 as revised), a global flag with PIQUELD_REQUEST_ID as fallback, names the command, and the CLI sends it as is as Idempotency-Key. Without either, the CLI uses a new UUID as before. No command sends more than one keyed mutation, so there is no per-mutation numbering.

  • True replay in the daemon: the receipt fingerprint now leaves out the preconditions a caller inspects beforehand (expected_generation, a save's expected_application_id) and keeps the mutation itself and force. A rerun with the same ID returns the original receipt and never repeats the change. The same ID with different content, or with --force added, still fails with request_id_conflict. Fingerprints keep their [mutation, null, force] shape, so receipts of requests sent without preconditions still match. Receipts last 24 hours, and those stored by an older daemon with a precondition conflict on a rerun across the upgrade instead of replaying: a safe failure, never a duplicate.

  • Scope: only changes to applications, environments and previews honor the key. Secret, notification, account, token and sign-in commands, and preview prune, ignore it and make their change again when rerun; the help and cli.md say so. preview deploy --local replays too: its snapshot commit is now dated like HEAD, so the same worktree makes the same commit.

  • IDs may be 1 to 128 characters, like the daemon accepts, through one shared is_request_id in piqueld-core.

cli.md, api.md and the flag help describe the behaviour.

Proof: a new daemon contract test reruns apply_and_deploy with the same ID and re-inspected preconditions after acceptance, and gets the original operation without a second change; a changed manifest under that ID conflicts. The store's legacy-receipt test now checks that another generation replays while force conflicts. The CLI retry test checks that --request-id agent-run-7 is sent as is on both attempts, and a snapshot test that an unchanged worktree snapshots to the same commit. The daemon's api_contract suite (100), its receipt, promotion and restore tests, cargo nextest run -p piquelctl -p piqueld-client -p piqueld-core (342), just lint and fmt pass.

Note

Written by Claude Opus 5.5 in Claude Code and reviewed by Claude Opus 5.5 (via T3 Code)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 35 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 875c1ec0-6b83-4916-9017-1f9cdea468a5
Commits

Reviewing files that changed from the base of the PR and between a1127cf and 392d745.

Files selected for processing: 15

  • apps/piquelctl/src/cli.rs
  • apps/piquelctl/src/commands.rs
  • apps/piquelctl/src/snapshot.rs
  • apps/piquelctl/tests/cli.rs
  • apps/piqueld/src/api/service/mod.rs
  • apps/piqueld/src/api/service/promotions.rs
  • apps/piqueld/src/api/service/restores.rs
  • apps/piqueld/tests/api_contract/acceptance.rs
  • crates/piqueld-client/src/lib.rs
  • crates/piqueld-core/src/lib.rs
  • crates/piqueld-core/src/names.rs
  • crates/piqueld-store/src/acceptance.rs
  • docs/user/reference/api.md
  • docs/user/reference/cli-commands.md
  • docs/user/reference/cli.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@PiquelChips
PiquelChips marked this pull request as ready for review November 10, 2026 18:50
@PiquelChips
PiquelChips force-pushed the feat/cli-request-id branch 3 times, most recently from dccd233 to 50acdda Compare November 12, 2026 21:33
- A global --request-id ID, else PIQUELD_REQUEST_ID, else a new UUID, is
  sent as the command's Idempotency-Key. Changes to applications,
  environments and previews honor it; secret, notification, account, token
  and sign-in commands, and preview prune, don't.
- The daemon's replay fingerprint leaves out the preconditions a caller
  inspects beforehand (expected generation, expected application ID), so a
  rerun with the same ID returns the original receipt instead of
  request_id_conflict, and never repeats the change. Different content or
  force under the same ID still conflicts.
- Local snapshot commits are dated like HEAD, so a rerun of
  preview deploy --local makes the same commit and replays.
- One is_request_id rule, 1 to 128 of [A-Za-z0-9-_.:], is shared by the
  daemon and the CLI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

1 participant