A batch of small command-line fixes, each with a regression test:
Dry run canceled while listing: says "Canceled.", prints no plan, exits 1 (was: empty plan, exit 0).
gclo accounts failures of the accounts file or Credential Manager: exit 2 with the reason, or 4 when another gclo process was changing the accounts (was: exit 70, "please report it").
gclo accounts -h is help; --description= is an empty description.
gclo accounts edit refuses a blank --rename, --org, --target.
A relative --target is stored as a full path.
The CLI's activity log follows GCLO_DATA_DIR, like accounts and settings (GcloPaths.LogDirectory).
Ctrl+C is held back only where something acts on it: sync, repos, orgs, and not while waiting at the token prompt (CancelKeyPolicy).
AccountsCommandTests: -h; empty description in both forms, on add and edit; blank values refused; relative target stored as a full path; a target that is not a path; file-write and credential-store failures → exit 2 with the cause attached and nothing half-changed; busy accounts file → exit 4 for every subcommand; an unrelated failure stays what it is.
SyncCommandTests: canceled dry run, text and JSON.
CancelKeyPolicyTests, OptionReaderTests, TokenOptionsTests (the process says it is waiting for input while the token is read, and not afterwards), GcloPathsTests (log folder under a moved data folder).
Local gates: locked zero-warning x64 build, 912 engine and 194 CLI tests, 100% line coverage on the three gated assemblies, dotnet format --verify-no-changes.
Behavior changes for scripts
gclo sync --dry-run interrupted during the listing now exits 1, not 0.
gclo accounts add|edit|remove now exits 2 (or 4) for storage failures, not 70.
An account's target is stored absolute; gclo accounts --json shows the full path.
With GCLO_DATA_DIR set, the CLI writes its log under that folder.
Notes for the reviewer
The Ctrl+C handler itself lives in Program (the composition root, excluded from coverage); the decision is in CancelKeyPolicy, which is tested.
Not in this change: AccountWizardViewModel.SaveAsync throwing synchronously (it belongs with the view-model work).
ITokenVault gains a member in #158; the test double added here (BrokenVault) will need it once both are merged.
Checklist
Commits are signed and signed off (git commit -S -s)
Zero build warnings; format and lint are clean
Tests added or updated; the 100% coverage gate still passes
Docs updated for user-visible changes; threat model updated if the attack surface changed (no change to the attack surface)
No secrets, credentials, or personal data
New dependencies are justified below, or none were added (none)
Substantial AI assistance, if any, is noted here: written with Claude Code, reviewed by an independent read-only agent before merge
## What
A batch of small command-line fixes, each with a regression test:
- **Dry run canceled while listing**: says "Canceled.", prints no plan, exits 1 (was: empty plan, exit 0).
- **`gclo accounts` failures of the accounts file or Credential Manager**: exit 2 with the reason, or 4 when another gclo process was changing the accounts (was: exit 70, "please report it").
- **`gclo accounts -h`** is help; **`--description=`** is an empty description.
- **`gclo accounts edit`** refuses a blank `--rename`, `--org`, `--target`.
- **A relative `--target`** is stored as a full path.
- **The CLI's activity log follows `GCLO_DATA_DIR`**, like accounts and settings (`GcloPaths.LogDirectory`).
- **Ctrl+C** is held back only where something acts on it: `sync`, `repos`, `orgs`, and not while waiting at the token prompt (`CancelKeyPolicy`).
## Why
Closes #159
## How it was tested
- `AccountsCommandTests`: `-h`; empty description in both forms, on add and edit; blank values refused; relative target stored as a full path; a target that is not a path; file-write and credential-store failures → exit 2 with the cause attached and nothing half-changed; busy accounts file → exit 4 for every subcommand; an unrelated failure stays what it is.
- `SyncCommandTests`: canceled dry run, text and JSON.
- `CancelKeyPolicyTests`, `OptionReaderTests`, `TokenOptionsTests` (the process says it is waiting for input while the token is read, and not afterwards), `GcloPathsTests` (log folder under a moved data folder).
- Local gates: locked zero-warning x64 build, 912 engine and 194 CLI tests, 100% line coverage on the three gated assemblies, `dotnet format --verify-no-changes`.
## Behavior changes for scripts
- `gclo sync --dry-run` interrupted during the listing now exits 1, not 0.
- `gclo accounts add|edit|remove` now exits 2 (or 4) for storage failures, not 70.
- An account's target is stored absolute; `gclo accounts --json` shows the full path.
- With `GCLO_DATA_DIR` set, the CLI writes its log under that folder.
## Notes for the reviewer
- The Ctrl+C handler itself lives in `Program` (the composition root, excluded from coverage); the decision is in `CancelKeyPolicy`, which is tested.
- Not in this change: `AccountWizardViewModel.SaveAsync` throwing synchronously (it belongs with the view-model work).
- `ITokenVault` gains a member in #158; the test double added here (`BrokenVault`) will need it once both are merged.
## Checklist
- [x] Commits are signed and signed off (`git commit -S -s`)
- [x] Zero build warnings; format and lint are clean
- [x] Tests added or updated; the 100% coverage gate still passes
- [x] Docs updated for user-visible changes; threat model updated if the attack surface changed (no change to the attack surface)
- [x] No secrets, credentials, or personal data
- [x] New dependencies are justified below, or none were added (none)
- [x] Substantial AI assistance, if any, is noted here: written with Claude Code, reviewed by an independent read-only agent before merge
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- `gclo sync --dry-run` canceled while the repositories were being
listed exited 0 with an empty plan. It now says it was canceled, prints
no plan, and exits 1.
- `gclo accounts`: a failure of the accounts file or of Windows Credential
Manager ended with exit code 70 ("please report it") where the help
promises 2. It is now a fatal error with its reason (2), or "retry
later" (4) when another gclo process was changing the accounts.
- `gclo accounts -h` was an unknown subcommand. `--description=` was
rejected although `--description ""` works.
- `gclo accounts edit` accepted a blank --rename, --org, or --target.
- A relative --target was stored as typed, so an account meant a
different folder depending on where it was used from. It is stored as a
full path.
- The CLI's activity log ignored GCLO_DATA_DIR while accounts and settings
followed it. The log folder is now "logs" under the data folder for the
app and the CLI alike (GcloPaths.LogDirectory).
- Ctrl+C: the first one was held back ("Canceling...") also for commands
that never look at the request (`accounts`) and while waiting at the
token prompt, and then nothing happened. It is held back only where
something acts on it (CancelKeyPolicy).
Closes#159
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
An edit of an account removed meanwhile, and credential-store or accounts
file failures in 'sync --account', are fatal errors with their reason, not
unexpected ones. A change to the accounts is let through to its end on
Ctrl+C. The target folder is checked before the token is read, and what
cannot be a folder is refused. A credential-store failure says what its
number means. Help and docs match.
Refs #159
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
Independent review (read-only agent): merge after fixes
The reviewer drove the command cores through the test seams with its own probes (no real vault, no network) and ran the CLI from source for the help and usage paths. Nothing regressed; two failures in this change's own theme still exited 70. What was done, in the second commit:
#
Finding
Resolution
M1
accounts edit of an account another gclo process removed meanwhile exited 70.
It is a fatal error (2) with the store's reason. Test added.
M2
sync --account did not get the storage-failure treatment: an unusable Credential Manager exited 70.
The mapping is shared (CliErrorException.IsStorageFailure / FromStorage) and applied where the account is opened and its token read. Tests added.
L1
One Ctrl+C could end accounts add/edit/remove between the credential store write and the file write.
For accounts the first Ctrl+C lets the change finish (with a notice that says so); at the token prompt, where nothing has started, it still ends the process.
L2
A bad --target was reported only after the token had been read.
Resolved and checked before anything is read or opened. The test asserts the token line is still unread.
L3
The target check accepted almost anything (a|b, con, ~, %USERPROFILE%\src).
Refused with a reason each: characters no folder name can have, a device name, unexpanded ~ / %NAME%, a colon anywhere but after a drive letter. Drive-relative and UNC paths still work.
L4
The credential-store message carried only a number.
What Windows says the number means is appended; the test double now mimics the real vault's message.
L5
Help and docs gaps (accounts exit codes, --target, canceled --dry-run --json, the development edition's log).
Updated.
L6
The "busy" test threw from a place the real store never does.
One test now takes the real path: the lock is held, the store waits, the command exits 4.
Checked and found sound by the reviewer: catch order and single log line per failure in all four accounts sites; -h and unknown-option parsing; RequireValueOrEmpty; blank-value checks; the canceled dry run with --account records no sync result; no new path prints or logs a token; the CLI's default log location is unchanged when GCLO_DATA_DIR is not set; the CLI test assembly does not run in parallel, so the console and working-directory tests cannot race.
Gates after the fixes: 912 engine and 211 CLI tests, 100% line coverage on the three gated assemblies, zero warnings, format clean.
Noted for other work: accounts list with an unreadable accounts file says "No accounts yet" (exit 0); the desktop wizard stores a relative target as typed; a canceled dry run after the listing still prints its plan.
## Independent review (read-only agent): merge after fixes
The reviewer drove the command cores through the test seams with its own probes (no real vault, no network) and ran the CLI from source for the help and usage paths. Nothing regressed; two failures in this change's own theme still exited 70. What was done, in the second commit:
| # | Finding | Resolution |
| --- | --- | --- |
| M1 | `accounts edit` of an account another gclo process removed meanwhile exited 70. | It is a fatal error (2) with the store's reason. Test added. |
| M2 | `sync --account` did not get the storage-failure treatment: an unusable Credential Manager exited 70. | The mapping is shared (`CliErrorException.IsStorageFailure` / `FromStorage`) and applied where the account is opened and its token read. Tests added. |
| L1 | One Ctrl+C could end `accounts add/edit/remove` between the credential store write and the file write. | For `accounts` the first Ctrl+C lets the change finish (with a notice that says so); at the token prompt, where nothing has started, it still ends the process. |
| L2 | A bad `--target` was reported only after the token had been read. | Resolved and checked before anything is read or opened. The test asserts the token line is still unread. |
| L3 | The target check accepted almost anything (`a\|b`, `con`, `~`, `%USERPROFILE%\src`). | Refused with a reason each: characters no folder name can have, a device name, unexpanded `~` / `%NAME%`, a colon anywhere but after a drive letter. Drive-relative and UNC paths still work. |
| L4 | The credential-store message carried only a number. | What Windows says the number means is appended; the test double now mimics the real vault's message. |
| L5 | Help and docs gaps (accounts exit codes, `--target`, canceled `--dry-run --json`, the development edition's log). | Updated. |
| L6 | The "busy" test threw from a place the real store never does. | One test now takes the real path: the lock is held, the store waits, the command exits 4. |
Checked and found sound by the reviewer: catch order and single log line per failure in all four `accounts` sites; `-h` and unknown-option parsing; `RequireValueOrEmpty`; blank-value checks; the canceled dry run with `--account` records no sync result; no new path prints or logs a token; the CLI's default log location is unchanged when `GCLO_DATA_DIR` is not set; the CLI test assembly does not run in parallel, so the console and working-directory tests cannot race.
Gates after the fixes: 912 engine and 211 CLI tests, 100% line coverage on the three gated assemblies, zero warnings, format clean.
Noted for other work: `accounts list` with an unreadable accounts file says "No accounts yet" (exit 0); the desktop wizard stores a relative target as typed; a canceled dry run after the listing still prints its plan.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
A batch of small command-line fixes, each with a regression test:
gclo accountsfailures of the accounts file or Credential Manager: exit 2 with the reason, or 4 when another gclo process was changing the accounts (was: exit 70, "please report it").gclo accounts -his help;--description=is an empty description.gclo accounts editrefuses a blank--rename,--org,--target.--targetis stored as a full path.GCLO_DATA_DIR, like accounts and settings (GcloPaths.LogDirectory).sync,repos,orgs, and not while waiting at the token prompt (CancelKeyPolicy).Why
Closes #159
How it was tested
AccountsCommandTests:-h; empty description in both forms, on add and edit; blank values refused; relative target stored as a full path; a target that is not a path; file-write and credential-store failures → exit 2 with the cause attached and nothing half-changed; busy accounts file → exit 4 for every subcommand; an unrelated failure stays what it is.SyncCommandTests: canceled dry run, text and JSON.CancelKeyPolicyTests,OptionReaderTests,TokenOptionsTests(the process says it is waiting for input while the token is read, and not afterwards),GcloPathsTests(log folder under a moved data folder).dotnet format --verify-no-changes.Behavior changes for scripts
gclo sync --dry-runinterrupted during the listing now exits 1, not 0.gclo accounts add|edit|removenow exits 2 (or 4) for storage failures, not 70.gclo accounts --jsonshows the full path.GCLO_DATA_DIRset, the CLI writes its log under that folder.Notes for the reviewer
Program(the composition root, excluded from coverage); the decision is inCancelKeyPolicy, which is tested.AccountWizardViewModel.SaveAsyncthrowing synchronously (it belongs with the view-model work).ITokenVaultgains a member in #158; the test double added here (BrokenVault) will need it once both are merged.Checklist
git commit -S -s)🤖 Generated with Claude Code
- `gclo sync --dry-run` canceled while the repositories were being listed exited 0 with an empty plan. It now says it was canceled, prints no plan, and exits 1. - `gclo accounts`: a failure of the accounts file or of Windows Credential Manager ended with exit code 70 ("please report it") where the help promises 2. It is now a fatal error with its reason (2), or "retry later" (4) when another gclo process was changing the accounts. - `gclo accounts -h` was an unknown subcommand. `--description=` was rejected although `--description ""` works. - `gclo accounts edit` accepted a blank --rename, --org, or --target. - A relative --target was stored as typed, so an account meant a different folder depending on where it was used from. It is stored as a full path. - The CLI's activity log ignored GCLO_DATA_DIR while accounts and settings followed it. The log folder is now "logs" under the data folder for the app and the CLI alike (GcloPaths.LogDirectory). - Ctrl+C: the first one was held back ("Canceling...") also for commands that never look at the request (`accounts`) and while waiting at the token prompt, and then nothing happened. It is held back only where something acts on it (CancelKeyPolicy). Closes #159 Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Signed-off-by: James Maes <james@kof22.com>Independent review (read-only agent): merge after fixes
The reviewer drove the command cores through the test seams with its own probes (no real vault, no network) and ran the CLI from source for the help and usage paths. Nothing regressed; two failures in this change's own theme still exited 70. What was done, in the second commit:
accounts editof an account another gclo process removed meanwhile exited 70.sync --accountdid not get the storage-failure treatment: an unusable Credential Manager exited 70.CliErrorException.IsStorageFailure/FromStorage) and applied where the account is opened and its token read. Tests added.accounts add/edit/removebetween the credential store write and the file write.accountsthe first Ctrl+C lets the change finish (with a notice that says so); at the token prompt, where nothing has started, it still ends the process.--targetwas reported only after the token had been read.a|b,con,~,%USERPROFILE%\src).~/%NAME%, a colon anywhere but after a drive letter. Drive-relative and UNC paths still work.--target, canceled--dry-run --json, the development edition's log).Checked and found sound by the reviewer: catch order and single log line per failure in all four
accountssites;-hand unknown-option parsing;RequireValueOrEmpty; blank-value checks; the canceled dry run with--accountrecords no sync result; no new path prints or logs a token; the CLI's default log location is unchanged whenGCLO_DATA_DIRis not set; the CLI test assembly does not run in parallel, so the console and working-directory tests cannot race.Gates after the fixes: 912 engine and 211 CLI tests, 100% line coverage on the three gated assemblies, zero warnings, format clean.
Noted for other work:
accounts listwith an unreadable accounts file says "No accounts yet" (exit 0); the desktop wizard stores a relative target as typed; a canceled dry run after the listing still prints its plan.