Repository navigation
Decide: make the command model read-only scan plus fix/undo/sync, with mode taken from project state, and make "nothing to undo" exit 0 #1088
Description
[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: discussion #560 register.
Kind: decision. Source: §4; Part 2.9; R6/R8 (register C34), plus a new finding (register C74). All verified on main @ 05ecc6e.
Questions
Q1. Should the verbs change? Options:
-
Keep the current verbs. Fix only the smaller seams: the help grouping, the two usage-error styles and the two warning renderers.
-
Mode becomes project state; the verbs stay. Bare
scan/getkeep the mode the project already has (vendored stays vendored). Switching mode, such as the in-place vendored→hosted takeover, needs an explicit--mode. Nothing is renamed. -
New command model in the next major:
New Replaces read-only scantoday's scan --dry-runfix [TARGET…] [--mode M]scan(write) andgetundo [TARGET…]rollback,removeandvendor --revertsyncapply,repairandscan --prunecheckapply --checkandvendor --checkThe old verbs stay as aliases for one major.
Recommendation: option 2 now, with option 3 decided for the next major. Option 2 is a small change to the default (≈ the mode-folding code in scan and get) and it stops the silent takeover. Option 3 needs the C11/C12 engine split first (#843, #894).
Q2. What should a command return when the project has no patch state? That covers a fresh project, or one that a full rollback just emptied. Options:
- A. Every whole-project command exits 0 with an empty result, so
rollbackandrepairbecome idempotent. - B. Keep today's per-command mix (table below).
- C. Every command exits 1 with
manifest_not_found.
Recommendation: A. Q2 is independent of Q1 and can land alone.
Problem
Q1: mode depends on unrelated flags and is not project state
scanis hosted by default, but--pruneor--globalmakes it report-only: scan/mod.rs#L228-L236.getis hosted by default, but--save-onlyor--globalmakes it agent mode: get.rs#L2676-L2682. Soget -g xpatches in place whilescan -gonly reports.- socket.yml refuses
modeas a trust-boundary key: socket_yml.rs#L1091-L1105. A barescanon a vendored project therefore runs the hosted vendored→hosted takeover in place for npm, cargo, golang, PyPI and Gradle-built Maven: takeover_capable, scan/hosted.rs#L1665-L1689. A project vendored for air-gapped installs becomes hosted the next time someone runs the quick-start command. rollbackandlistalready infer mode from on-disk state.- Overlapping verbs:
get <pkg>≈scan --package <pkg>;remove <id>≈rollback <id>;vendor≈scan --mode vendored;vendor --revert≈rollbackof vendored state;repairis not thescan --pruneGC.
GlobalArgsflattens 27 flags into 11 commands.
Q2: "nothing to undo" exits differently per command (new finding, C74)
The fixture is a bare directory with no .socket/. The run used a debug build under env -i HOME=… PATH=/usr/bin:/bin, with --json, twice, and gave identical results both times:
| Command | Exit | Output |
|---|---|---|
list |
0 | success, empty |
apply |
0 | noManifest |
vendor --revert |
0 | success |
rollback |
1 | legacy {"status":"error","error":"Manifest not found"} |
repair |
1 | manifest_not_found |
vex |
2 | manifest_not_found (C58, #1047) |
The rollback exit is in rollback.rs#L1065-L1145. The contract calls it "the legacy … exit 1".
v5's default modes write no manifest: hosted keeps no ledger, and an emptied vendor ledger deletes state.json. So a successful rollback leaves exactly this state, and the second rollback exits 1. A test pins that today, under the name "idempotence": e2e_redirect_vlt_build.rs#L1185-L1210. The two post-install entry points also disagree on a project with nothing patched yet: apply exits 0 and repair exits 1.
Impact
- Q1: a silent mode change on the most common command, and users and CI templates that expect a "scan" to be read-only.
- Q2: CI scripts that run
rollbackorrepairfail on a re-run or on a fresh clone, depending on which verb they picked.
Proposed change
- Q1 option 2:
- When the project holds vendored or agent state and no
--modeis given,scan/getkeep that mode. - The hosted takeover runs only under an explicit
--mode hosted. - This deletes the "bare scan takes over" path, and the contract sentence "vendored → hosted conversions both work in place" gains "only with
--mode".
- When the project holds vendored or agent state and no
- Q2 option A:
rollback's legacyManifest not foundbranch becomesstatus: successwith zeros. That deletes the legacy JSON shape.repairon an empty project matchesapply(noManifest, exit 0).remove <target>keeps exit 1, but withnot_found, because a named target that matches nothing is still an error.
Size and scope
- Q2: ~60 production lines in
rollback.rsandrepair.rs, plus the contract rows and the tests that pin exit 1 (in_process_rollback_hosted.rs,mode_migration_vlt.rs,e2e_redirect_vlt_build.rs, the covgap rollback/repair suites). - Q1 option 2: ~100 lines in the mode folding, plus contract and tests.
- Option 3 is a tracking issue of its own after Tracking: split run_scan into discover, select, one mode dispatch and render phases #843 and Tracking: move the patch engines out of the CLI command modules into core #894.
- Out of scope: the JSON error shape (Decide: one shape for the
--jsontop-levelerror(scan and get emit both a string and a {code, message} object) #704) and vex's exit 2 (Decide: should vex exit 1 on runtime failures, like every other command, instead of 2 #1047).
Acceptance criteria
- A maintainer answers Q1 and Q2 on this issue.
- Q2 (if A): running
rollback,repairandvendor --reverttwice in a row exits 0 both times on hosted-only, vendored-only and empty fixtures, with a regression test per fixture. - Q1 (if 2): a bare
scanon a vendored npm project leaves the vendor ledger and lockfile byte-identical, andscan --mode hostedstill takes over. Both get regression tests. - The contract tables are updated in the same PRs.
Dependencies
- Option 3 is blocked by Tracking: split run_scan into discover, select, one mode dispatch and render phases #843 (C11) and Tracking: move the patch engines out of the CLI command modules into core #894 (C12).
- Related decisions: Decide: warn on and then remove scan --apply/--vendor, and whether --vex stays embedded #966 (deprecated spellings) and Decide: keep agent mode for every ecosystem, or limit it to Deno and --global installs #1000 (agent mode kept).
Activity
audit-core: file C34 decision (#1088, with C74), C32 tracking (#1089, #…
mikolalysenko commentedon Oct 7, 2026
[agent] Triaged:
priority:p3(CLI command-model decision). It's a design decision for a maintainer, so it keepsagent:needs-humanand isn't eligible for an agent claim.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). Narrow v5 scope to the normal default: a bare scan/get should not silently convert an existing vendored/agent project to hosted mode. Settle/document explicit takeover semantics before v5. The wholesale fix/undo/sync rename and repeated-empty-run idempotence are not release requirements.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 9, 2026
[agent] Claiming for v5 blocker burn-down (shared root cause: bare scan/get default mode ignores existing project state; maintainer's 2026-10-09 triage comment narrows scope to option 2). Branch: agent/v5-mode-from-state. Claim-ID: 2026-10-09T16:42:28Z-710d9d
Keep the project's mode on a bare scan or get