Skip to content

feat(servers): expose branch, auto-deploy and atomic deployment flags - #38

Merged
thdurante merged 3 commits into
mainfrom
thiago/dhq-691-expose-server-branch-auto-deploy-and-atomic-deployment
Aug 5, 2026
Merged

feat(servers): expose branch, auto-deploy and atomic deployment flags#38
thdurante merged 3 commits into
mainfrom
thiago/dhq-691-expose-server-branch-auto-deploy-and-atomic-deployment

Conversation

@thdurante

@thdurante thdurante commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds five first-class deployment-configuration flags to dhq servers create and dhq servers update, so automation no longer has to drop to the generic dhq api escape hatch:

--branch  --auto-deploy  --atomic  --atomic-strategy  --atomic-retention

A shared serverDeploymentFlags helper backs both commands so they cannot drift apart. Only explicitly-supplied flags are sent, so an update never disturbs a setting the operator did not name. Booleans and retention reach the SDK as pointers: an omitted flag stays off the wire, an explicit --auto-deploy=false still serialises as false, and a zero retention never reaches the backend's >= 1 validation. Strategy and retention are validated locally before the project is resolved and the API client is built, so a malformed invocation fails offline with no credentials touched.

Companion API PR: deployhq/deployhq#1106 — corrects the published request schema this client depends on. Worth landing together: the spec currently types auto_deploy as string and omits the atomic fields entirely, so the CLI's (correct) request shape fails validation against the published document.

Resolves DHQ-691.

Behaviour verified against the Rails source

Rather than assume, each rule was traced to backend code:

Behaviour Evidence
--branch "" unpins a server IGNORE_PARAMS_ON_BLANK is credential-only (server_params.rb:7), so a blank branch is permitted and persisted; every consumer resolves it with .presence
--atomic locked after first deploy ServerConcerns::Atomic#can_update_atomic?deployments.first.nil?
retention >= 1 server.rb:103 numericality validation
strategies are exactly copy_release / copy_cache servers_helper.rb:19-20; column defaults to copy_release
atomic silently stripped for non-atomic accounts servers_controller.rb:683,698 — permitted only when atomic_deployments_allowed?; validate_atomic_allowed is guarded by if: :atomic_changed?, so it never fires

Two silent-failure modes now surfaced

Both were previously "succeeds with exit 0, changed nothing". Warnings go to stderr; stdout stays pure data.

  1. Atomic requested but not applied. The create/update response is the read-back the docs asked operators to perform, so the CLI compares intent against the returned server instead of delegating to a reference doc an agent may never load. No extra request.
  2. --branch on a grouped server. The backend resolves server_group.branch || server.branch || repository.branch, and grouped servers are excluded from auto-deployment entirely — so a branch stored on a grouped server never deploys. Rails hides the field in its UI; the API does not.

Test plan

  • go build ./cmd/dhq/
  • go vet ./...
  • go test ./...871 passing (was 853 on main)
  • go test -race ./... — 871 passing
  • Local validation exercised against the real binary: invalid strategy and retention 0 / -1 return structured user_error / exit_code: 1 with recovery hints

Mutation-tested, not just green. Two regressions were introduced deliberately to confirm the new tests actually catch them:

  • deleting both applyToCreate/applyToUpdate call sites previously left the entire suite green — it now fails
  • disabling the Managed VPS --accept-cost billing gate now fails

Review round

Addressed on-PR feedback; all three threads resolved.

Reviewer Finding Outcome
Codex Managed VPS examples in servers.md passed --json but omitted --accept-cost, so they hard-fail before creating anything Fixed in 681f3bc — also corrected the pre-existing "My VPS" example carrying the same defect on main
CodeRabbit blockNetwork writes called without synchronisation — data race Skipped, refuted: the only goroutine is in SendTelemetry, invoked after cmd.Execute() returns; and every caller asserts require.Error, so cobra returns before PersistentPostRun and called is never written. -race -count=3 → 57 passed, no race
CodeRabbit MD028 blank line between blockquotes Skipped — nothing lints markdown in this repo (ci.yml runs golangci-lint only), and the two warnings are meant to render as separate blocks

The --accept-cost fix was verified against the built binary with credentials resolvable, so the run actually reaches the gate: without the flag it stops at "Managed VPS creation requires --accept-cost"; with it the request reaches the API.

Backward compatibility

  • No change to public API response shape
  • No change to existing CLI command output/behaviour when the new flags are absent
  • pkg/sdk change is additive — five new fields on ServerCreateRequest/ServerUpdateRequest; no existing field altered

Documentation

  • skills/deployhq/references/servers.md — flag table, both silent-failure warnings, unpinning, two-environment worked example
  • skills/deployhq/SKILL.md — atomic gotchas in the always-loaded entry point, so an agent that never opens the reference still gets the irreversibility warning
  • skill-evals/ — 5 new cases; also fixed an existing eval that could not fail for the footgun it guarded
  • CHANGELOG.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01Hibw5xsRNGDkYzr1hXsDQz

Summary by CodeRabbit

  • New Features

    • Added server deployment settings for branch selection, automatic deployment, atomic deployments, strategies, and release retention.
    • Added SDK support for preserving explicitly provided false, empty, and zero values.
    • Added validation and warnings for unsupported strategies, grouped-server branch behavior, and unavailable atomic deployments.
  • Documentation

    • Added command examples and guidance for configuring, verifying, and updating deployment settings.
    • Updated the unreleased changelog.

thdurante and others added 2 commits August 5, 2026 08:52
Add five first-class deployment-configuration flags to `dhq servers create`
and `dhq servers update`, so automation no longer has to drop down to the
generic `dhq api` escape hatch:

  --branch, --auto-deploy, --atomic, --atomic-strategy, --atomic-retention

A shared `serverDeploymentFlags` helper backs both commands so they cannot
drift apart. Only flags the operator explicitly supplied are sent, so an
update never disturbs a setting that was not named. Booleans and retention
reach the SDK as pointers: an omitted flag stays off the wire while an
explicit `--auto-deploy=false` still serialises as `false`, and a zero
retention is never sent into the backend's `>= 1` validation.

Validation of `--atomic-strategy` and `--atomic-retention` runs before the
project is resolved and before the API client is built, so a malformed
invocation fails offline with no credentials touched.

Branch handling, verified against the DeployHQ Rails source:

  * `--branch ""` unpins a server so it falls back to the repository default.
    `IGNORE_PARAMS_ON_BLANK` covers only credential params, so the backend
    accepts and persists a blank branch; every consumer resolves it with
    `.presence`. `Branch` is therefore a `*string` — as a plain string with
    omitempty the empty value was dropped and the command reported success
    having changed nothing. Note the API echoes it back as `""`, not null.

  * Setting `--branch` on a server in a server group now warns on stderr.
    The backend resolves the branch as
    `server_group.branch || server.branch || repository.branch`, and grouped
    servers are excluded from auto-deployment entirely, so a branch stored on
    a grouped server never deploys. The write succeeds and is echoed back,
    making it a silent no-op; Rails hides the field in its UI, the API does
    not. stdout stays pure data.

Tests: adds a capturing transport that pins the command -> request seam end
to end. Both call sites could previously be deleted with the entire suite
still green; the new tests fail when the wiring is removed (mutation-tested).
Suite goes 853 -> 863, clean under -race and go vet.

Refs DHQ-691

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hibw5xsRNGDkYzr1hXsDQz
… fixture

Follow-up to the DHQ-691 review. Four issues, all verified against the
DeployHQ Rails source or by control test.

Report the silent atomic strip instead of documenting it. An account without
atomic deployments enabled has `atomic`, `atomic_strategy` and
`atomic_retention` removed by the backend's permit list before any validation
runs, so the call returns 2xx with atomic off and no error. The CLI already
holds the create/update response — which is exactly the read-back the docs
told operators to perform — so it now compares intent against the returned
server and warns on stderr. No extra request; stdout stays pure data. The
three params are permitted as a group, so checking `atomic` covers all of
them; the other two atomic failure modes (unsupported protocol, change after
the first deployment) return real validation errors and need no client check.

Pin the Managed VPS billing guard with a regression test. The `--accept-cost`
gate is the only thing between a non-interactive invocation and a billable
provisioning call, and this branch inserted statements on both sides of it.
The pre-existing test only asserted the flag was registered and never ran the
command. The new test drives the real command with a network tripwire, so it
fails if the guard is ever moved below the API call (mutation-verified).

Make the atomic-before-first-deployment eval able to fail. run-evals.sh only
checks that a response contains the expected command, so a response emitting
both the deployments-list check and the forbidden `servers update --atomic`
scored as a pass — the eval could not fail for the footgun it was added to
catch. Adds the matching must_not_contain.

Correct a fabricated value in the golden fixture. integration_test.go carried
`"atomic_strategy": "symlink"` in two places, but no such strategy exists in
the backend: the column defaults to copy_release, the only behavioural branch
tests for copy_cache, the UI offers just those two, and no migration mentions
symlink. CLAUDE.md advertises that file as validating against real API JSON,
so the fixture was actively misleading.

Suite goes 863 -> 871, clean under -race and go vet.

Refs DHQ-691

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hibw5xsRNGDkYzr1hXsDQz
@linear

linear Bot commented Aug 5, 2026

Copy link
Copy Markdown

DHQ-691

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

Server deployment configuration

Layer / File(s) Summary
SDK deployment request contracts
pkg/sdk/types.go, pkg/sdk/server_deployment_settings_test.go, pkg/sdk/integration_test.go
Server create and update requests now support optional branch, auto-deploy, atomic, strategy, and retention fields. Tests verify JSON nesting, explicit values, and omitted fields.
CLI flag validation and server wiring
internal/commands/servers.go, internal/commands/servers_test.go
servers create and servers update register deployment flags, validate values before network access, apply explicit settings, default create atomic strategy to copy_release, and warn about unapplied settings.
Documentation and evaluation coverage
CHANGELOG.md, skills/deployhq/SKILL.md, skills/deployhq/references/servers.md, skill-evals/deployhq/evals.json
Documentation and evaluations cover deployment flags, restrictions, grouped-server behavior, branch clearing, verification, and atomic deployment rules.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as servers create/update
  participant Validation as deployment flag validation
  participant Request as ServerCreateRequest/ServerUpdateRequest
  participant DeployHQAPI
  CLI->>Validation: validate supplied deployment flags
  Validation->>Request: apply explicit deployment settings
  Request->>DeployHQAPI: create or update server
  DeployHQAPI-->>CLI: return server response
  CLI->>CLI: emit dormant or unapplied setting warnings
Loading
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change by identifying the new server deployment flags.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch thiago/dhq-691-expose-server-branch-auto-deploy-and-atomic-deployment

Comment @coderabbitai help to get the list of available commands.

@thdurante thdurante self-assigned this Aug 5, 2026
@thdurante

Copy link
Copy Markdown
Contributor Author

@codex review this pr

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/sdk/server_deployment_settings_test.go (1)

16-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move handler assertions to the test goroutine. require calls FailNow, which must run in the test goroutine. Record the method and decode error in each handler, then assert them after the client call. Replacing require with assert does not move the assertions out of the handler goroutine.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/sdk/server_deployment_settings_test.go` around lines 16 - 34, Update
captureCreateBody and captureUpdateBody so their HTTP handlers only record the
request method and JSON decode error, without calling require or assert. After
each client call in the associated tests, assert the recorded method and decode
error from the test goroutine, preserving the existing request validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/commands/servers_test.go`:
- Around line 28-39: Update blockNetwork to use sync/atomic.Bool for called,
storing true atomically in the transport callback and returning the atomic flag
rather than *bool. Adjust every called assertion to use Load(), add the
sync/atomic import, and preserve go vet and race-test compatibility.

In `@skills/deployhq/references/servers.md`:
- Around line 73-75: Fix the MD028 violation between the two blockquotes by
replacing the blank line with a non-blockquote separator, such as a normal
paragraph separator, so the validation-error and --branch warning blocks remain
distinct.

---

Nitpick comments:
In `@pkg/sdk/server_deployment_settings_test.go`:
- Around line 16-34: Update captureCreateBody and captureUpdateBody so their
HTTP handlers only record the request method and JSON decode error, without
calling require or assert. After each client call in the associated tests,
assert the recorded method and decode error from the test goroutine, preserving
the existing request validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da085057-1fae-4cc6-a120-65f75fb8c7fa

📥 Commits

Reviewing files that changed from the base of the PR and between fc0524a and d816eab.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • internal/commands/servers.go
  • internal/commands/servers_test.go
  • pkg/sdk/integration_test.go
  • pkg/sdk/server_deployment_settings_test.go
  • pkg/sdk/types.go
  • skill-evals/deployhq/evals.json
  • skills/deployhq/SKILL.md
  • skills/deployhq/references/servers.md

Comment thread internal/commands/servers_test.go
Comment thread skills/deployhq/references/servers.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d816eaba8b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skills/deployhq/references/servers.md
Every `dhq servers create --protocol-type managed_vps` example in the servers
reference passed `--json` but omitted `--accept-cost`, so each one fails before
creating anything:

  "Managed VPS creation requires --accept-cost (free for early customers
   during beta, billed monthly afterwards)"

The guard at internal/commands/servers.go:514-519 fires whenever the session is
non-TTY, non-interactive, or in JSON mode, so the examples break in any agent or
CI context, not only when --json is present. Verified against the built binary
with credentials resolvable: without the flag the command stops at the gate,
with it the request reaches the API.

Fixes the two examples added by this branch's worked two-environment section,
plus the pre-existing "My VPS" example above them, which carried the same defect
on main and would have been left broken next to the corrected ones.

Caught by Codex on #38.

Refs DHQ-691

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hibw5xsRNGDkYzr1hXsDQz
@thdurante
thdurante merged commit 59d9855 into main Aug 5, 2026
17 checks passed
@thdurante
thdurante deleted the thiago/dhq-691-expose-server-branch-auto-deploy-and-atomic-deployment branch August 5, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants