chore: release main - #100
Merged
Merged
Conversation
github-actions
Bot
force-pushed
the
release-please--branches--main
branch
4 times, most recently
from
August 18, 2026 15:36
b279407 to
35678b6
Compare
Contributor
|
Hold merge: #102 must land first — it fixes the dependency floors this release is about to freeze onto PyPI, and its BREAKING CHANGE footer re-versions this release to 0.3.0. Once #102 merges and release-please regenerates this PR: run |
GregHolmes
added a commit
that referenced
this pull request
Aug 19, 2026
…t-code + error-stream correctness (#102) Blocks #100 — merge this first, then let release-please regenerate the release PR as **0.3.0**. ## What this fixes **1. `dg update` on pip silently doesn't deliver the release (root `pyproject.toml`).** Root declared floors as low as `>=0.0.1`, and pip's default `only-if-needed` upgrade strategy leaves any sub-package whose installed version already satisfies its floor. Measured from published `deepctl==0.2.26` with the release wheels available: only 4 of 17 released packages upgraded; `dg --version` reported the new number while the keys fixes — including the `-o json` fix that headlines the release — never arrived. (uv resolves fresh and is unaffected, so the same `dg update` lands two users in different states.) Floors now match the versions being published. **2. Eight packages can import a symbol their declared core floor doesn't guarantee (`packages/*/pyproject.toml`).** `deepctl-cmd-{billing,keys,members,models,projects,read,requests,usage}` import `get_status_console` (new in core 0.2.16) at module scope while declaring `deepctl-core>=0.1.10`. PyPI's latest published core is 0.2.14, so `pip install --upgrade deepctl-cmd-keys` alone reproduces a broken CLI: the command vanishes and the ImportError prints to stdout. Same class of hand-bump as #92; release-please has no cross-package dependency automation, so these floors are hand-maintained. **3. A crash exited 2 — the code reserved for user interrupt (`src/deepctl/main.py`).** Per the published contract (0 = success, 1 = error, 2 = user interrupt), `main()`'s generic exception handler now exits 1. Because `cli()` runs with `standalone_mode=False`, Click usage errors (bad flag, unknown command, bare `dg`) propagate to this same handler and move from 2 to 1 as well — consistent with the contract, which reserves 2 for interrupt. Also repairs three tests whose `patch.object(cli, "__call__", ...)` was inert (dunder lookup bypasses instance attributes) and adds a usage-error exit-code test. **4. …but that alone demoted a real Ctrl-C to 1 (`src/deepctl/main.py`).** With `standalone_mode=False`, Click catches a `KeyboardInterrupt` raised during command execution and re-raises it as `click.exceptions.Abort` — a `RuntimeError` subclass, not a `KeyboardInterrupt`. In this repo the path is more direct still: `BaseCommand` catches the interrupt itself and raises `click.Abort()`. So a mid-command Ctrl-C — the common case — bypassed the `KeyboardInterrupt` handler entirely and landed in the generic handler that 3 just changed, exiting 1 and printing an empty `Error: ` (`str(Abort())` is `""`). Before 3 that path exited 2 correctly by accident. `Abort` is now caught alongside `KeyboardInterrupt`, so user cancellation (Ctrl-C, Ctrl-D at a prompt) always exits 2, with a test on the `Abort` delivery path. **5. Root diagnostics printed to stdout, corrupting `-o json` (`src/deepctl/main.py`).** `main()`'s module-level console was a plain rich `Console()`, which writes to stdout, and both of its handlers print through it — so a crash, bad flag, unknown command, or bare `dg` wrote human-readable prose to stdout. `dg -o json not-a-command` put `Error: No such command ...` on **stdout** and left stderr empty, so anything piping stdout into `jq` parsed the error text instead of JSON. This is the root-handler half of the #97 sweep: that issue's scope covered moving errors to a stderr console "so stdout stays clean", and #101 closed the sweep, but both only ever reached the command layer. Now aliases `deepctl_core.output.stderr_console` — the same console `print_error()` writes to, and the pattern `deepctl-cmd-mcp` already follows — so root-level and command-level diagnostics format identically, including the no-color handling for agentic/CI callers. Exit codes are unchanged and success paths still write their payload to stdout. Adds tests asserting a failing `dg -o json ...` writes nothing to stdout (unknown-command and bad-flag paths) and that the cancellation notice is on stderr. ## Why the BREAKING CHANGE footer The exit-code enforcement (#101) landed as `fix:`, so release-please would ship it as patch 0.2.28 with no version signal — and #100 currently confirms that: root reads `0.2.28` and its diff contains no `⚠ BREAKING CHANGES` section at all. There is no machine-readable breaking marker anywhere in the cycle; the behavior change exists only as hand-written prose in `21b8333` / `e327a5e`. The break belongs to #101's already-merged code, so it cannot come from a conventional-commit type on this PR's own diff — it has to be injected where the version arithmetic can see it. With `bump-minor-pre-major`, that makes root **0.3.0**. The signal is deliberately stated in three places, because which one release-please actually reads depends on how this PR is merged: - **`BREAKING CHANGE:` footer on `914e132`** — the primary. That commit touches only `src/deepctl/main.py` and its tests, i.e. a root-only path, so the break is attributed to root alone and the eight sub-packages stay patch bumps. This is the one that survives a **merge commit**, and it is the only variant that produces the intended release shape. - **`fix!:` in the title** — insurance. This repo's recent PRs were squash-merged, and with `squash_merge_commit_title: PR_TITLE` / `squash_merge_commit_message: PR_BODY` a squash discards every commit message, footer included. The `!` keeps root at 0.3.0 in that case. - **`BREAKING CHANGE:` footer at the foot of this description** — so a squash also carries the descriptive text into the `⚠ BREAKING CHANGES` section rather than just the subject line. Trade-off to know before merging: **prefer a merge commit.** A squash collapses all five commits into one that touches root *and* the eight `packages/deepctl-cmd-*/pyproject.toml` files, so the break gets attributed to those eight paths too — they would take minor bumps (keys 0.0.3 → 0.1.0, usage 0.1.13 → 0.2.0, …) with a breaking-change entry about CLI exit codes that has nothing to do with them, and root's floors here (`>=0.0.4`) would then sit below what actually published, re-seeding the staleness this PR exists to fix. Root reaches 0.3.0 either way; only the sub-package shape differs. ## After merge — steps on the regenerated #100 1. `uv lock` and commit (version bumps stale the lock; CI runs `uv sync --locked` — this is the `eff5291` wall and recurs every release until release.yml regenerates the lock itself). 2. Re-apply the behavior-change prose (`git show 21b8333 e327a5e`), changing `0.2.26 to 0.2.28` → `0.2.26 to 0.3.0`, and fold in the exit-code and output-stream details from this PR: crashes and usage errors move 2 → 1, `2` stays reserved for user interrupt (Ctrl-C during a command still exits 2), and root error/cancellation output moves from stdout to stderr. Decide there whether the stream move gets its own `⚠ BREAKING CHANGES` line or reads as a plain fix — root lands on 0.3.0 either way, so it is a notes-wording call, not a version call. 3. Verify root reads 0.3.0 across manifest / `pyproject.toml` / `__init__.py` / `CHANGELOG` heading, and the `⚠ BREAKING CHANGES` section renders the footer text. Verification here: full suite **1094 passed / 6 skipped**; `make check` clean (ruff + mypy, 115 files); `uv lock --check` clean after both floor commits; live probes — `--version` → 0, bare `dg` → 1, bad flag → 1, unknown command → 1, mid-command Ctrl-C → 2; `dg -o json not-a-command` writes **0 bytes** to stdout with the error on stderr, while `dg -o json models` still emits valid JSON on stdout. 🤖 Generated with [Claude Code](https://claude.com/claude-code) BREAKING CHANGE: `dg` now exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.
github-actions
Bot
force-pushed
the
release-please--branches--main
branch
from
August 19, 2026 11:39
e327a5e to
10252f7
Compare
…e publishes Two edits the release PR needs before it can go green and deliver correctly. uv.lock: release-please rewrote `version = ...` in 11 pyproject.toml files, which stales the lock's pinned workspace members. Every test job starts with `uv sync --locked`, so all 16 failed in ~10s before running a test. This is the recurring eff5291 wall and it fires on every release until release.yml regenerates the lock itself. Root floors: the eight command packages landed on minor bumps rather than the patches the floors in #102 were computed against, because the #102 merge commit carried `fix!:` plus the BREAKING CHANGE footer and its first-parent diff spans all eight package pyproject.toml files -- so release-please attributed the break to each of them. keys/members/models/read/requests/ billing went to 0.1.0 and projects/usage to 0.2.0, leaving root's floors below what publishes. Delivery still worked this cycle (each floor sits above the version currently on PyPI, so pip is forced to upgrade), but it re-seeded exactly the staleness #102 set out to fix: next cycle a user on 0.1.0 would satisfy `>=0.0.4` and pip would skip the upgrade. Floors now match the manifest exactly. Verified: `uv lock --check` and `uv sync --locked` clean, `make check` clean (ruff + mypy, 115 files), 1094 passed / 6 skipped, and every root floor equal to its manifest version for all 17 packages publishing a new version.
…ullet Re-applies the prose from 21b8333 and e327a5e, which release-please discarded when it regenerated this PR, updated for 0.3.0 and for the changes that landed since: * The generated `⚠ BREAKING CHANGES` list carried the exit-code paragraph twice. The footer exists both on 914e132 and on the #102 merge commit (whose body is the PR description, where the footer was repeated as insurance against a squash), and both touch root paths, so release-please emitted it once per commit. Deduped to one. The eight command packages list it once each and are unaffected. * `### Behavior changes` now covers usage errors exiting 1, interrupt staying 2, and error/cancellation output moving from stdout to stderr, alongside the yaml/csv and `keys` items from the original prose. Framed as additions to the breaking entry above rather than restating it. * `### Previously unreleased` reads 0.2.26 -> 0.3.0, and records that the pip floor repair is what makes this release actually arrive. * deepctl-core keeps its three behavior notes, with the `get_status_console()` entry now naming the `>=0.2.16` floor its importers need. Mirrored into the release PR body so the published notes and the committed changelog say the same thing.
Contributor
Author
GregHolmes
added a commit
that referenced
this pull request
Aug 19, 2026
Review of #103 found the guard trustworthy for the case it was written for and quietly wrong outside it. Rule 3 (new): root's dependency list must cover every published package. Rule 1 only validated the floors already listed, so a package that release-please versions and publishes but that nobody added to root's dependencies was invisible -- `pip install --upgrade deepctl` never installs it at all. That is the same delivery gap #100/#102 were about, through the one door the guard left open, and the repo adds command packages regularly. NOT_SHIPPED carries the two deliberate exclusions so the intent is stated in the diff rather than inferred from an omission. --fix no longer reports success after failing. The rewrite matched the literal `"name>=X.Y.Z"` including both quotes, so it silently no-opped on any spec with an upper bound, extra, or environment marker -- and the fix branch never recorded the miss, so the script printed "dependency floors OK" and exited 0 on a file it had not touched. It now rewrites the version inside the matched spec (preserving the rest) and falls through to `problems` when it cannot, which also puts the previously-unused third element of the floors() tuple to work. Rule 3 backstops this: a spec form the regex cannot parse at all now surfaces as a missing root dependency instead of being skipped. vkey() no longer dies on PEP 440 suffixes. A single hand-set 0.4.0rc1 anywhere in the workspace turned `make floors-check` into a bare ValueError traceback naming no package.
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.
🤖 I have created a release beep boop
0.3.0
0.3.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
Behavior changes
Alongside the exit-code change above, upgrading to 0.3.0 changes these:
0= success,1= error,2= user interrupt. Crashes and usage errors (bad flag, unknown command, baredg) exit1;2is reserved for cancellation, so Ctrl-C during a running command and Ctrl-D at a prompt both still exit2.dg -o json …therefore keeps stdout machine-readable when a command fails — previously a failure printedError: …prose to stdout, so a script piping stdout intojqparsed the error text instead of JSON. Successful commands still write their payload to stdout.-o yamland-o csvno longer drop square-bracketed text from values. Output was passed through a renderer that read[...]as style markup and deleted it, so an API key comment of[ci] runnerwas emitted asrunner. Long values are also no longer hard-wrapped mid-field.dg keys --delete KEY_IDnow asks for confirmation on stderr instead of always reportingCancelled by userwithout deleting. In a non-interactive context it exits1and tells you to pass--yes.dg keys --create --dry-runnow reports what it would create. It previously failed with an internalTypeError.Previously unreleased
0.2.27 was tagged on 2026-08-17 but never reached PyPI — its publish step failed with
InvalidDistribution: Invalid distribution metadata: '2.5' is not a valid metadata version, which #94 and #95 then fixed. PyPI therefore goes straight from 0.2.26 to 0.3.0, and this release is the first published build to include the 0.2.27 changes:flux-alexis-en) instead of Aura 2 (#89) (5a0b698). This changes the default model fordg speak, so synthesised audio differs unless you pass anaura-*model explicitly.Six packages tagged in that cycle also reach PyPI for the first time here:
deepctl-cmd-listen0.0.14,deepctl-cmd-login0.1.17,deepctl-cmd-skills0.0.7,deepctl-cmd-speak0.0.4,deepctl-cmd-update0.2.6 anddeepctl-telemetry0.0.6.Because 0.2.27 never published,
dg updateon pip also had to be repaired for this release to arrive at all: root's inter-package dependency floors were lower than the versions being published, so pip's defaultonly-if-neededstrategy left most sub-packages stale anddg --versionreported the new number while the fixes never landed. Floors now match the published versions exactly.deepctl-core: 0.2.16
0.2.16 (2026-08-19)
Bug Fixes
Behavior changes
error→1,cancelled→2, otherwise0), andBaseCommand.exit_code_for()exposes that mapping. Exit codes were previously discarded, so every command exited0.-o yamland-o csvpayloads are written verbatim; the renderer no longer interprets[...]as markup or wraps long values.get_status_console()returns the shared stderr console for status output. Commands should use it instead of declaring their own. Packages that import it requiredeepctl-core>=0.2.16.deepctl-cmd-projects: 0.2.0
0.2.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
deepctl-cmd-usage: 0.2.0
0.2.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
deepctl-cmd-mcp: 0.1.15
0.1.15 (2026-08-19)
Bug Fixes
deepctl-cmd-models: 0.1.0
0.1.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
deepctl-cmd-keys: 0.1.0
0.1.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
deepctl-cmd-read: 0.1.0
0.1.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
deepctl-cmd-requests: 0.1.0
0.1.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
deepctl-cmd-billing: 0.1.0
0.1.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
deepctl-cmd-members: 0.1.0
0.1.0 (2026-08-19)
⚠ BREAKING CHANGES
dgnow exits non-zero when a command fails: 1 for errors (including crashes and usage errors), 2 for user interrupt, 0 on success. Every command previously exited 0 regardless of outcome, so scripts and CI steps that ignored the exit code will surface failures they were silently swallowing. No command that succeeds changes its exit code.Bug Fixes
This PR was generated with Release Please. See documentation.