Skip to content

Release v0.30.0-beta7#274

Merged
tis24dev merged 18 commits into
mainfrom
dev
Jul 24, 2026
Merged

Release v0.30.0-beta7#274
tis24dev merged 18 commits into
mainfrom
dev

Conversation

@tis24dev

@tis24dev tis24dev commented Jul 24, 2026

Copy link
Copy Markdown
Owner

Automated release PR for v0.30.0-beta7.

Summary by Sourcery

Switch relay secret provisioning and self-heal to a dedicated /api/relay/provision endpoint with rate-limit backoff, enhance daemon and healthcheck handling for parked hosts, and add a post-upgrade flow that shows release notes from the newly installed binary.

New Features:

  • Add a dedicated Telegram-independent relay provisioning flow using POST /api/relay/provision with structured contracts for success, already-provisioned, and rate-limited responses.
  • Introduce a --show-whatsnew CLI mode and upgrade hook that re-invokes the installed binary to display Screen 0 (what's new) after successful interactive upgrades.

Enhancements:

  • Refine daemon relay secret self-healing with typed rate-limit errors, server-directed Retry-After plus deterministic jitter, and clearing secrets when the server parks a host.
  • Extend healthcheck and Telegram relay paths to recognize parked hosts alongside auth failures and classify them distinctly in the setup UI.
  • Improve notification logging across Telegram, email, Gotify, webhooks, and the notification adapter so terminal failures are summarized once and inner loggers demote noisy warnings to debug.
  • Render disabled audit components in a multi-line bulleted column for better readability in the post-install result screen.
  • Document and constrain serverbot response headers and propagate cloned headers to callers that need endpoint-specific backoff information.
  • Clarify stream task help text to better describe full-log copying and selection behavior.

Tests:

  • Add comprehensive tests for the relay provision endpoint contract, rate-limit parsing and clamping, daemon backoff behavior, parked-host handling, and serverbot header cloning.
  • Add AST-based and behavioral tests to ensure the post-upgrade whats-new screen is always hooked after a successful upgrade and correctly gated by interactivity and auto-yes flags.
  • Expand CLI, healthcheck classification, install audit, dashboard, and Telegram relay tests to cover new flags, parked-host semantics, UI layout, and error handling.

Summary by CodeRabbit

  • New Features
    • Added --show-whats-new mode to display release highlights and exit.
    • After successful interactive upgrades, the app now automatically shows the “What’s New” screen once.
  • Bug Fixes
    • Relay credentials are now cleared and re-provisioned when the server rejects secrets as invalid or reports the host as parked.
    • Relay-secret provisioning retries now honor server backoff/rate limits with deterministic, bounded scheduling.
  • UI Improvements
    • Streamed backup now reliably replays earlier run output so the panel starts from the beginning.
    • Improved post-install “disabled components” formatting and updated streamed UI help text.
  • Notifications & Logging
    • Reduced duplicate/noisy notification logging while keeping a single clear failure line visible.

Greptile Summary

This release updates relay provisioning and parked-host recovery, adds post-upgrade release notes, replays dashboard log backlogs, and refines notification logging and install UI behavior.

  • Moves relay-secret provisioning to the dedicated /api/relay/provision endpoint with typed rate-limit backoff.
  • Adds parked-host classification and daemon credential self-healing.
  • Adds an installed-binary --show-whatsnew handoff after successful interactive upgrades.
  • Improves streamed backup output, notification diagnostics, and post-install audit presentation.

Confidence Score: 4/5

The PR is not yet safe to merge because losing a local secret for an already-provisioned host still leaves relay notifications and centralized healthchecks unavailable indefinitely.

The updated branch detects and warns about the orphaned confirmed registration but neither restores credentials nor initiates a recovery transition, so daemon retries continue returning no usable secret.

internal/notify/relay_provision.go

Important Files Changed

Filename Overview
internal/notify/relay_provision.go Introduces the dedicated provisioning contract and rate-limit handling, but the lost-local-secret already_provisioned state still has no client-driven recovery.
cmd/proxsave/daemon.go Adds parked-host credential clearing and bounded provisioning retries, while callers continue retrying an unrecoverable empty-secret response.
cmd/proxsave/upgrade.go Correctly gates the post-upgrade release-notes handoff on successful binary and configuration upgrades.
internal/health/config.go Adds parked-host classification for centralized healthcheck configuration responses.
cmd/proxsave/backup_stream.go Replays pre-viewport log output before attaching live streamed backup capture.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Daemon needs relay secret] --> B[POST /api/relay/provision]
  B -->|201 with secret| C[Persist and confirm secret]
  B -->|429| D[Schedule server-directed retry]
  B -->|200 already_provisioned| E[Warn and return without secret]
  E --> F[Centralized fetch remains unavailable]
  F --> G[Retry after local throttle]
  G --> B
Loading

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Reviews (7): Last reviewed commit: "test(dashboard): apply De Morgan to the ..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

tis24dev added 9 commits July 22, 2026 20:06
…e dashboard (Maintenance > Upgrade) instead of the CLI command
The what's-new screen previously appeared only on the next bare dashboard
launch. Re-invoke the freshly installed binary with a new internal
--show-whatsnew mode from upgradeFinalizePhase (after the footer, on
success), so Screen 0 opens at the end of the upgrade rendered by the binary
that actually carries the notes: the notes registry is compiled into each
binary, so on the download path -- where the OLD binary finalizes -- the hook
hands off to the installed binary via execPath, which knows the new notes.

Gate to interactive, non-auto-yes runs. Skip when the operator signalled an
unattended run -- proxsave --upgrade y, or upgrade-beta.sh -y (now propagated
to the finalize as `--upgrade y --localfile`) -- so an automated upgrade under
an allocated pty (ssh -tt, Ansible, script -c) never stalls on Screen 0 until
the 10-minute timeout. Skip under --dry-run so the new non-bare --show-whatsnew
path never writes the seen-flag.

Tests: AST guard that the hook runs after printUpgradeFooter under the success
gate; the interactive/auto-yes/dry-run gate matrix; the --upgrade y --localfile
ordering contract the script depends on; the mode dispatch gate.
…ut help line

The run-output panel only advertised 'c copy log' (whole log). Clarify that
'c' copies the FULL log and that a shift+drag selects a section for the
terminal's own copy (the dashboard holds the mouse via MouseModeCellMotion, so
shift is what bypasses capture for native selection). Text-only; no behavior
change.
…ion+drag)

macOS terminals (iTerm2, Terminal.app) bypass mouse capture with Option, not
Shift, so the native-selection hint now reads 'shift/option+drag' to cover both
Linux/Windows (Shift) and macOS (Option). Text-only.
…a column

The 'UPDATED' outcome joined every disabled key into one comma-separated line,
which the styled result box truncated on a long list (17 keys showed as one
'BACKUP_..., BA...' line). Render a header line plus one '- KEY' per line so the
full list is readable. SanitizeText preserves newlines, so each key gets its own
row. Update the integration assertion to the two-part format and add a unit test
pinning the column layout.
Each notification channel logged a failure twice: the notifier self-logged its
terminal failure AND the NotificationAdapter logged its own outcome line for the
same result. Make the adapter the single voice for a channel's terminal outcome
and demote every notifier's terminal-failure self-log to Debug (the failure is
already returned in the result the adapter reads).

The adapter's generic complete-failure branch now carries the reason at WARNING
("<name>: <error>") instead of a terse "failure reported" with the detail hidden
at Debug. logTelegramOutcome is unchanged, so the relay two-line result and the
two Telegram outcome tests are untouched; the webhook inner "failed after N
attempts" line is demoted because the caller loop already logs each failed
endpoint once. Mid-flow lines (fallback attempts, per-attempt retries, PMF
continue-on-warning, auth-rotation recovery) stay as-is: they are not duplicates.

Levels are otherwise unchanged (survivor stays WARNING, not NOTIFY-ERR), so run
recap counts, Prometheus warnings_total, the pre-dispatch notification snapshot,
exit code, and result-derived sensor status are unaffected. The run-log parser
(classifyLogLine/ParseLogCounts) shared with the what's-new pipeline is untouched.
…provisioning

The relay now answers 410 SERVER_PARKED when a purged host still presents
its old relay token (design 11.2). This is the phase-4 precondition: an
Option-A host whose unused account was parked must clear the stale token
and re-provision when it returns.

- health/config.go: add ErrHCParked sentinel + map HTTP 410 to it on
  /api/healthcheck/config.
- daemon.go buildReporter: clear the on-disk relay secret on ErrHCParked,
  mirroring the existing ErrHCAuth self-heal (value-guarded against the
  exact secret used, so a concurrently minted fresh secret is never
  clobbered). The next run re-provisions; the host is re-admitted as a
  recreation.
- notify/telegram.go: add errRelayParked + map HTTP 410 in sendViaRelay;
  Send treats it like errRelayAuthRejected (drop the in-memory secret and
  re-provision this run). Bounded, no loop.
- orchestrator/healthcheck_setup_classify.go: PARKED install message.

Legacy v0.29 clients use /api/get-chat-id and never receive 410, so they
are unaffected. Tests added in config_test, telegram_test,
daemon_relay_provision_test, healthcheck_setup_test; affected packages pass.
…hat-id

Blocker: ProvisionRelaySecret used the legacy GET /api/get-chat-id
handshake, which for a chat-less (Telegram-independent) server returns
409 SERVER_NON_ASSOCIATO before ever issuing a token. Option A could
therefore never obtain a relay secret, and SERVER_PARKED recovery could
never re-provision.

- provisionViaRelay(): POST /api/relay/provision with {server_id} and the
  shared X-Proxsave-Version header (unauthenticated: the endpoint issues
  the token). Handles 201 notify_secret (adopt), 200 already_provisioned
  (nothing to adopt, never reads a missing token), 429 (retryable refusal),
  and any other status as an error, never embedding the untrusted body.
- ProvisionRelaySecret keeps the same lock / re-check / persist / confirm
  bricks; only the HTTP call changed. get-chat-id remains the unchanged
  legacy/Telegram/v0.29 path.

Tests rewritten in relay_provision_test.go for the POST contract: method,
exact path, body carries server_id, X-Proxsave-Version present, no
X-Server-Auth, 201 persist+confirm, 200 already_provisioned no-op, 429
retryable no-persist, 201-no-token, short-secret, empty-baseDir, adopt
under lock. Affected client packages pass.
Comment thread cmd/proxsave/daemon.go Fixed
@sourcery-ai

sourcery-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

This release refactors relay secret provisioning to a dedicated POST /api/relay/provision endpoint with robust rate limiting and retry logic, introduces post-upgrade "what's new" display plumbing and CLI flag handling, improves healthcheck handling of parked servers, and rebalances notification-channel logging to centralize terminal outcome reporting while tightening UI and install-flow behaviors.

Sequence diagram for relay secret provisioning with rate limiting

sequenceDiagram
    actor Operator
    participant daemon as Daemon
    participant provisionAttempt as provisionRelaySecretAttempt
    participant notify as ProvisionRelaySecret
    participant relay as provisionViaRelay
    participant serverbot as serverbot.Client
    participant server as proxsave_server

    Operator->>daemon: heartbeat loop
    daemon->>daemon: maybeProvisionRelaySecret
    daemon->>provisionAttempt: provisionRelaySecretAttempt(cfg, logger)
    provisionAttempt->>notify: ProvisionRelaySecret(serverAPIHost, serverID, baseDir, logger)
    notify->>relay: provisionViaRelay(ctx, serverAPIHost, serverID, logger)
    relay->>serverbot: Client.Do(Request{POST /api/relay/provision})
    serverbot->>server: POST /api/relay/provision {server_id}
    alt 201 Created
        server-->>serverbot: 201 {notify_secret}
        serverbot-->>relay: Response{Status:201, Header, Body}
        relay-->>notify: secret, alreadyProvisioned=false, nil
        notify-->>provisionAttempt: (persist+confirm), nil
        provisionAttempt-->>daemon: secret, retryAfter=0
    else 200 OK already_provisioned
        server-->>serverbot: 200 {status:"already_provisioned"}
        serverbot-->>relay: Response{Status:200, Header, Body}
        relay-->>notify: "", alreadyProvisioned=true, nil
        notify-->>provisionAttempt: "", 0
        provisionAttempt-->>daemon: "", retryAfter=0
    else 429 TooManyRequests
        server-->>serverbot: 429 Retry-After
        serverbot-->>relay: Response{Status:429, Header, Body}
        relay-->>notify: error RelayProvisionRateLimitError{RetryAfter}
        notify-->>provisionAttempt: "", RetryAfter
        provisionAttempt-->>daemon: "", RetryAfter
        daemon->>daemon: provisionRetryAt = now + RetryAfter + daemonProvisionRetryJitter(serverID, RetryAfter)
    else other status
        server-->>serverbot: non-2xx
        serverbot-->>relay: Response{Status, Header, Body}
        relay-->>notify: error (unexpected status)
        notify-->>provisionAttempt: "", 0
        provisionAttempt-->>daemon: "", 0
    end
Loading

Sequence diagram for post-upgrade what's-new screen invocation

sequenceDiagram
    actor Operator
    participant upgradeSh as upgrade-beta.sh
    participant proxsaveOld as proxsave(old)
    participant proxsaveNew as proxsave(new)
    participant whatsMode as runShowWhatsnewMode
    participant whatsScreen as showWhatsnewScreen
    participant dashboard as maybeShowWhatsnew

    Operator->>upgradeSh: run upgrade-beta.sh
    upgradeSh->>proxsaveOld: proxsave --upgrade --localfile
    proxsaveOld->>proxsaveOld: upgradeFinalizePhase
    alt upgradeErr == nil
        proxsaveOld->>proxsaveNew: runWhatsnewAfterUpgrade(execPath, args)
        proxsaveNew->>whatsMode: dispatchPreRuntimeModes (--show-whatsnew)
        whatsMode->>whatsScreen: showWhatsnewScreen(ctx, args, toolVersion)
        whatsScreen->>dashboard: maybeShowWhatsnew(ctx, session, baseDir, toolVersion)
        dashboard-->>Operator: Screen 0 (what's new), then exit
    else upgradeErr != nil
        proxsaveOld-->>Operator: footer with error, no Screen 0
    end
Loading

File-Level Changes

Change Details Files
Refactor relay secret provisioning to use POST /api/relay/provision with structured responses, rate-limit error handling, and adoption of existing secrets under a lock.
  • Introduce relayProvisionServer test helper and relayCapture struct to validate HTTP method, path, headers, body, and confirm behavior.
  • Add provisionViaRelay helper that calls serverbot.Client.Do, parses JSON, handles 201/200/429/other statuses, and registers secrets with the logger.
  • Add RelayProvisionRateLimitError type and parseRelayProvisionRetryAfter with clamping to avoid time.Duration overflow.
  • Update ProvisionRelaySecret to use provisionViaRelay instead of get-chat-id, retain locking/adopt-existing-secret behavior, and enforce defensive secret length and persistence rules.
  • Expand relay_provision tests to cover happy path, already_provisioned 200, malformed 200, 429 with Retry-After, confirm failures, missing token, short secret, empty baseDir, and adoption of existing secrets.
internal/notify/relay_provision.go
internal/notify/relay_provision_test.go
Enhance daemon relay-secret self-heal to honor server Retry-After with deterministic jitter, and clear secrets on both auth and parked errors.
  • Rename daemon.provisionLast to provisionRetryAt and use it as a timestamp for the next allowed provisioning attempt.
  • Introduce daemonProvisionRetryInterval and daemonProvisionMaxRetryJitter constants plus daemonProvisionRetryJitter(serverID, retryAfter) to compute stable bounded jitter per server.
  • Refactor provisionRelaySecretBestEffort into provisionRelaySecretAttempt returning a secret and Retry-After, interpreting RelayProvisionRateLimitError and logging backoff.
  • Update maybeProvisionRelaySecret to install local throttle before network calls, then extend provisionRetryAt based on server Retry-After plus jitter.
  • Extend buildReporter centralized fetch path to treat health.ErrHCParked like ErrHCAuth, clearing the on-disk secret via RemoveNotifySecretIfMatches.
  • Add daemon_relay_provision tests for parked clearing, backoff honoring, jitter stability, and throttle behavior.
cmd/proxsave/daemon.go
cmd/proxsave/daemon_relay_provision_test.go
internal/health/config.go
internal/health/config_test.go
Add post-upgrade "what's new" screen support, including CLI flag, mode dispatch, and a structural test to ensure Screen 0 runs after successful upgrades.
  • Add Args.ShowWhatsnew flag and CLI parsing for --show-whatsnew.
  • Introduce whatsnewAfterUpgradeInteractive, shouldRunWhatsnewAfterUpgrade, and runWhatsnewAfterUpgrade to re-invoke the installed binary with --show-whatsnew after successful upgrades when interactive and not auto-yes.
  • Wire runWhatsnewAfterUpgrade into upgradeFinalizePhase after printUpgradeFooter and add upgrade-beta.sh logic to pass a y auto-yes token to --upgrade when ASSUME_YES is set.
  • Add showWhatsnewScreen to run only Screen 0 using shell.Session, delegating to maybeShowWhatsnew with dry-run and interactivity guards.
  • Register runShowWhatsnewMode in dispatchPreRuntimeModes to handle --show-whatsnew and always exit success.
  • Add whatsnew_upgrade_test.go for AST-level guard of the upgradeFinalizePhase hook, gating logic tests, and mode behavior tests, and args_test coverage for UpgradeAutoYes with --localfile and ShowWhatsnew flag.
internal/cli/args.go
internal/cli/args_test.go
cmd/proxsave/upgrade.go
cmd/proxsave/main_modes.go
cmd/proxsave/dashboard.go
cmd/proxsave/whatsnew_upgrade_test.go
upgrade-beta.sh
Improve healthcheck classification and UI messaging for parked hosts, and columnar rendering of disabled components in post-install audit.
  • Add ErrHCParked sentinel in health config and treat HTTP 410 SERVER_PARKED as parked in fetchConfig.
  • Update HealthcheckSetup classification to map ErrHCParked to a PARKED fatal result with explanatory text.
  • Change RunPostInstallAudit to use disabledComponentsSummary, which formats component keys as a header line plus one bulleted line per key.
  • Add tests for parked classification and disabled component summary layout, including dashboard audit result expectations.
internal/health/config.go
internal/health/config_test.go
internal/orchestrator/healthcheck_setup_classify.go
internal/orchestrator/healthcheck_setup_test.go
internal/ui/flows/install/audit.go
internal/ui/flows/install/audit_test.go
internal/ui/flows/install/install_test.go
Adjust notification-channel logging (Telegram, email, Gotify, webhook) so per-channel adapters centralize terminal outcome reporting while individual notifiers log detailed failures at Debug level.
  • Introduce errRelayParked sentinel in Telegram notifier, treat HTTP 410 SERVER_PARKED as parked, and align sendViaRelay and Send behavior to drop the secret and reprovision on auth/parked while demoting transport errors to Debug.
  • Update EmailNotifier to log detection, recipient validation, root mailbox, and send failures at Debug, leaving WARNING-level messaging to the orchestrator adapter, and adjust fallback-success logging.
  • Change GotifyNotifier error handling to use Debug logs for configuration and send failures.
  • Change WebhookNotifier endpoint retry exhaustion log from Error to Debug to avoid double-reporting per-endpoint failure.
  • Update NotificationAdapter.Notify to emit WARNING with the notifier name and error for terminal failure, relying on notifiers’ Debug logs for details.
  • Add tests for Telegram parked relay sentinel behavior and log redaction coverage.
internal/notify/telegram.go
internal/notify/telegram_test.go
internal/notify/email.go
internal/notify/gotify.go
internal/notify/webhook.go
internal/orchestrator/notification_adapter.go
Extend serverbot client response to expose cloned headers for consumers such as relay provisioning and add tests to ensure header cloning behavior.
  • Extend serverbot.Response to include Header http.Header alongside Status and Body.
  • Update Client.Do to populate Response.Header using resp.Header.Clone() before returning.
  • Use Response.Header in relay provision logic to read Retry-After for rate limiting.
  • Add client_test coverage to ensure headers are cloned and not aliased to the transport response.
internal/serverbot/request.go
internal/serverbot/client.go
internal/serverbot/client_test.go
Polish UI help text for stream task logs to clarify selection and copy behavior.
  • Update StreamTask.Help strings for done and running states to mention copying the full log and using shift/option+drag to select sections.
  • Ensure help text still distinguishes continue vs cancel key bindings.
internal/ui/components/streamtask.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai sourcery-ai 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.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@tis24dev

Copy link
Copy Markdown
Owner Author

@CodeRabbit review

Comment thread cmd/proxsave/upgrade.go Outdated
Comment thread internal/notify/relay_provision.go
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.82517% with 36 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/logging/capture.go 0.00% 17 Missing ⚠️
internal/logging/logger.go 11.11% 6 Missing and 2 partials ⚠️
internal/notify/telegram.go 33.33% 5 Missing and 1 partial ⚠️
internal/notify/gotify.go 40.00% 3 Missing ⚠️
internal/notify/email.go 75.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds dedicated relay provisioning with parked-server recovery and rate-aware retries, introduces Screen 0 upgrade integration, consolidates notification failure logging, replays pre-viewport dashboard logs, and updates installation and task output.

Changes

Relay recovery and provisioning

Layer / File(s) Summary
Relay provisioning contracts
internal/health/*, internal/notify/relay_provision.go, internal/serverbot/*, internal/notify/relay_provision_test.go
Relay provisioning now uses /api/relay/provision, typed rate-limit errors, capped Retry-After parsing, cloned response headers, persistence checks, and confirmation handling.
Parked-server relay handling
internal/notify/telegram.go, internal/notify/telegram_test.go, internal/orchestrator/*, cmd/proxsave/daemon_relay_provision_test.go
HTTP 410 responses produce parked sentinels, clear stale credentials, trigger re-provisioning, and classify setup as fatal.
Daemon retry scheduling
cmd/proxsave/daemon.go, cmd/proxsave/daemon_relay_provision_test.go
Centralized relay retries use next-deadline throttling, server backoff, deterministic jitter, and value-guarded secret clearing.

Screen 0 upgrade flow

Layer / File(s) Summary
Screen 0 mode entry
internal/cli/*, cmd/proxsave/main_modes.go
Adds the internal --show-whatsnew flag and pre-runtime dispatch path.
Screen 0 renderer
cmd/proxsave/dashboard.go, cmd/proxsave/whatsnew_upgrade_test.go
Screen 0 skips non-interactive and dry-run execution, creates a dashboard session, and delegates to the existing decision flow.
Post-upgrade Screen 0 hook
cmd/proxsave/upgrade.go, cmd/proxsave/whatsnew_upgrade_test.go, upgrade-beta.sh
Successful interactive upgrades invoke the installed binary after the footer; unattended upgrade forwarding and final instructions are updated.

Notification and interface output

Layer / File(s) Summary
Notification reporting levels
internal/notify/*, internal/orchestrator/notification_adapter.go
Notifier-level duplicate failure logs move to debug while the adapter emits a generic warning and debug failure details.
Install and task output
internal/ui/components/streamtask.go, internal/ui/flows/install/*
Disabled components are rendered as a bulleted multiline summary, and stream-task help includes full-log copying and section selection instructions.

Dashboard stream backlog

Layer / File(s) Summary
Pre-viewport log replay
internal/logging/*, cmd/proxsave/main_runtime.go, cmd/proxsave/backup_stream.go, cmd/proxsave/backup_stream_test.go
A bounded colored log backlog is mirrored during dashboard handoff and replayed before live backup-stream capture.

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

Sequence Diagram(s)

sequenceDiagram
  participant UpgradeFinalize
  participant InstalledProxsave
  participant WhatsnewScreen
  UpgradeFinalize->>InstalledProxsave: invoke --show-whatsnew after successful upgrade
  InstalledProxsave->>WhatsnewScreen: run interactive Screen 0 flow
  WhatsnewScreen-->>InstalledProxsave: render or skip based on terminal and dry-run state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 matches the PR’s main purpose: releasing v0.30.0-beta7.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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

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

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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

🤖 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/notify/webhook.go`:
- Around line 474-477: Demote the per-endpoint failure log in
WebhookNotifier.Send to Debug so it does not duplicate the
notification_adapter.go aggregate Warning. Keep the existing endpoint failure
details and control flow unchanged, with the adapter remaining the single
terminal user-visible failure log.

In `@internal/orchestrator/notification_adapter.go`:
- Around line 79-85: Update the complete-failure branch in the notification
adapter to avoid logging raw result.Error at Warning level. Keep the warning
message generic and, if diagnostic details are needed, emit the unsanitized
error only at Debug; preserve the existing generic warning when no error is
present.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 97babcb1-0ab1-4f1d-9225-b499977c7fd4

📥 Commits

Reviewing files that changed from the base of the PR and between af512a8 and b44bf4e.

📒 Files selected for processing (28)
  • cmd/proxsave/daemon.go
  • cmd/proxsave/daemon_relay_provision_test.go
  • cmd/proxsave/dashboard.go
  • cmd/proxsave/main_modes.go
  • cmd/proxsave/upgrade.go
  • cmd/proxsave/whatsnew_upgrade_test.go
  • internal/cli/args.go
  • internal/cli/args_test.go
  • internal/health/config.go
  • internal/health/config_test.go
  • internal/notify/email.go
  • internal/notify/gotify.go
  • internal/notify/relay_provision.go
  • internal/notify/relay_provision_test.go
  • internal/notify/telegram.go
  • internal/notify/telegram_test.go
  • internal/notify/webhook.go
  • internal/orchestrator/healthcheck_setup_classify.go
  • internal/orchestrator/healthcheck_setup_test.go
  • internal/orchestrator/notification_adapter.go
  • internal/serverbot/client.go
  • internal/serverbot/client_test.go
  • internal/serverbot/request.go
  • internal/ui/components/streamtask.go
  • internal/ui/flows/install/audit.go
  • internal/ui/flows/install/audit_test.go
  • internal/ui/flows/install/install_test.go
  • upgrade-beta.sh

Comment thread internal/notify/webhook.go
Comment thread internal/orchestrator/notification_adapter.go Outdated
tis24dev added 5 commits July 24, 2026 12:48
gosec (G115, CWE-190) flags the uint64 -> int64 conversion in
daemonProvisionRetryJitter. The value is provably bounded (the modulo keeps it
below window <= 5min), but a structural fix is preferred over a #nosec: mask the
FNV sum to 63 bits so it is always a valid non-negative int64, then take the
modulo in signed space. Behavior is unchanged (deterministic, bounded jitter);
only the conversion is made statically safe.
Raise coverage on the new POST /api/relay/provision client. Adds cases for the
rate-limit error message (both branches + nil receiver), every
parseRelayProvisionRetryAfter branch (absent, non-positive seconds, unparseable,
integer seconds, and the HTTP-date future/past/beyond-cap paths), and the
provisionViaRelay/ProvisionRelaySecret failure modes: unexpected status (body
not leaked into the error), malformed 201 JSON, transport error, lock failure on
a file baseDir, and a real read error on the under-lock re-check.

relay_provision.go statement coverage rises from ~64% to ~98%.
Two related review findings on the single-terminal-voice logging refactor:

- notification_adapter.go promoted result.Error to WARNING. That error can carry
  raw upstream response bodies (webhook.go embeds string(body) in its 400/403/5xx
  errors), so a warning could surface an arbitrary endpoint's reply in stderr and
  recorded logs. Keep the WARNING generic (as the fallback branch already is) and
  emit the unsanitized detail only at Debug.
- webhook.go logged each failed endpoint at Error in the caller loop while the
  adapter warns once on the aggregate, double-reporting a single failure. Demote
  that per-endpoint line to Debug so the adapter stays the single terminal voice
  (this also stops the wrapped raw body from surfacing above Debug here).
…l upgrade

The Screen 0 hook gated on upgradeErr == nil (binary install) alone, so a good
binary install followed by a FAILED config upgrade still opened the celebratory
what's-new screen while the footer showed 'Configuration: ERROR' and the command
returned a nonzero exit. Require cfgUpgradeErr == nil too. Update the structural
AST guard to pin the two-condition gate.
Rewrite the two-condition gate matcher from !((A && B) || (C && D)) to the De
Morgan form (two named booleans, !forward && !reverse) so staticcheck's QF1001
quickfix is not triggered. No behavior change.

@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: 1

🤖 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 `@cmd/proxsave/whatsnew_upgrade_test.go`:
- Around line 38-39: Rewrite the compound condition around isErrNilCheck so it
uses the De Morgan–simplified form or clear named predicates, while preserving
the requirement that land.X and land.Y contain upgradeErr and cfgUpgradeErr in
either order. Ensure the resulting expression satisfies golangci-lint QF1001.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e1f3729a-77ce-4771-a8c9-d1db6455e793

📥 Commits

Reviewing files that changed from the base of the PR and between b44bf4e and e5b6d5f.

📒 Files selected for processing (6)
  • cmd/proxsave/daemon.go
  • cmd/proxsave/upgrade.go
  • cmd/proxsave/whatsnew_upgrade_test.go
  • internal/notify/relay_provision_test.go
  • internal/notify/webhook.go
  • internal/orchestrator/notification_adapter.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • cmd/proxsave/upgrade.go
  • cmd/proxsave/daemon.go

Comment thread cmd/proxsave/whatsnew_upgrade_test.go Outdated
Greptile flagged that ProvisionRelaySecret silently returns success when the
server answers already_provisioned while the local secret is gone, leaving each
retry stuck. The server already re-mints for an UNCONFIRMED row (201), so this
branch is specifically the confirmed-then-lost case: the server keeps only the
secret's sha256 and cannot return the plaintext, and re-minting a confirmed
token for an unauthenticated caller would let anyone knowing the 16-digit
server_id rotate another host's secret. There is no safe in-band recovery, so
raise a WARNING with the remediation (recovers automatically once the chat-less
row becomes purge-eligible, or an operator clears it) instead of a silent Debug.
Assert the warning fires in the already_provisioned test.
Comment thread internal/notify/relay_provision.go
…t from orchestrator init

A backup launched from the interactive dashboard streamed its log into a
contained viewport that started mid-run at "Initializing backup orchestrator",
losing every line logged before the viewport existed: the version banner,
environment detection, config load, "Log file opened", server identity, and the
security preflight. Those lines are on disk via two logger paths - logWithLabel
(console+file) and AppendRaw (file-only, used by bootstrap.Flush for the raw
banner) - but the viewport only tapped the console, which the dashboard handoff
mutes and which is captured only once backupStreamSteps begins.

Add a colored `mirror` sink on Logger, written on BOTH logWithLabel and
AppendRaw, parallel to the file sink and independent of the muted console.
initializeRunLogger attaches it (into a bounded LineBacklog) on a dashboard
handoff, before bootstrap.Flush, so it captures the whole pre-viewport stream in
file order. runBackupStreamed replays that backlog into the panel and detaches
the mirror before the live capture is installed, so no line is double-captured.
The viewport now shows the same complete, ordered, colored stream the log file
has. CLI/cron/daemon runs are unchanged (mirror never attached; nil-guarded).

@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

🤖 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 `@cmd/proxsave/backup_stream_test.go`:
- Around line 574-578: Update the test around the three waitFor calls to assert
ordering as well as presence: after all expected strings are available, obtain
each substring’s index in buf and require the ProxSaveBanner index to precede
Environment: dual, which must precede Initializing backup orchestrator.

In `@cmd/proxsave/backup_stream.go`:
- Around line 55-61: The mirror-to-live-capture handoff in captureRunOutput
currently leaves a gap where concurrent logger output is discarded. Serialize or
atomically coordinate clearing the mirror, replaying backlog.Lines(), and
installing the live capture so each line is delivered exactly once to either the
replay sink or viewport, with no interval using io.Discard.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cd8e418c-d2d3-4907-91b9-4b1cb24f82e5

📥 Commits

Reviewing files that changed from the base of the PR and between e5b6d5f and c7339d4.

📒 Files selected for processing (8)
  • cmd/proxsave/backup_stream.go
  • cmd/proxsave/backup_stream_test.go
  • cmd/proxsave/main_runtime.go
  • cmd/proxsave/whatsnew_upgrade_test.go
  • internal/logging/capture.go
  • internal/logging/logger.go
  • internal/notify/relay_provision.go
  • internal/notify/relay_provision_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • cmd/proxsave/whatsnew_upgrade_test.go
  • internal/notify/relay_provision.go
  • internal/notify/relay_provision_test.go

Comment thread cmd/proxsave/backup_stream_test.go
Comment on lines +55 to +61
// Detach the mirror while the console is still muted and before the live pipe
// exists: from here the run's remaining lines reach the panel only through the
// capture the caller installs next, so nothing is double-captured.
logging.GetDefaultLogger().SetMirror(nil)
for _, line := range backlog.Lines() {
emit(line)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Prevent log loss during the mirror-to-live-capture handoff.

After Line 58 clears the mirror, the logger still writes to io.Discard until captureRunOutput is installed at Line 164. Any concurrent log in that interval is absent from both the replay and the viewport. Make this transition atomic/serialized so every line reaches exactly one sink.

🤖 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 `@cmd/proxsave/backup_stream.go` around lines 55 - 61, The
mirror-to-live-capture handoff in captureRunOutput currently leaves a gap where
concurrent logger output is discarded. Serialize or atomically coordinate
clearing the mirror, replaying backlog.Lines(), and installing the live capture
so each line is delivered exactly once to either the replay sink or viewport,
with no interval using io.Discard.

… present

TestRunBackupStreamedReplaysPreStreamBacklog waited for the three
substrings (banner, environment, orchestrator init) with presence-only
polls, so a regression that streamed the live step line before the
replayed backlog would still pass. Add an index comparison requiring
banner < environment < init on the captured frame, matching the on-disk
log order the fix guarantees.

@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.

🧹 Nitpick comments (1)
cmd/proxsave/backup_stream_test.go (1)

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

Resolve the QF1001 finding.

Rewrite the negated conjunction using De Morgan’s law; this preserves behavior and clears the reported staticcheck finding.

Proposed fix
-	if !(iBanner >= 0 && iBanner < iEnv && iEnv < iInit) {
+	if iBanner < 0 || iBanner >= iEnv || iEnv >= iInit {
🤖 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 `@cmd/proxsave/backup_stream_test.go` at line 588, Update the condition
involving iBanner, iEnv, and iInit to apply De Morgan’s law instead of negating
the conjunction, preserving the same boolean behavior while resolving the QF1001
staticcheck finding.

Source: Linters/SAST tools

🤖 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.

Nitpick comments:
In `@cmd/proxsave/backup_stream_test.go`:
- Line 588: Update the condition involving iBanner, iEnv, and iInit to apply De
Morgan’s law instead of negating the conjunction, preserving the same boolean
behavior while resolving the QF1001 staticcheck finding.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 07ec2ede-057c-49d5-af52-e48fca0b5c63

📥 Commits

Reviewing files that changed from the base of the PR and between c7339d4 and 126e5bd.

📒 Files selected for processing (1)
  • cmd/proxsave/backup_stream_test.go

…QF1001)

The negated compound condition tripped golangci-lint QF1001. Rewrite
!(a && b && c) as the equivalent disjunction so CI passes; behavior
unchanged.
Comment on lines +200 to 207
if logger != nil {
logger.Warning("Relay provisioning: server_id is already provisioned server-side " +
"but no local relay secret is present; a confirmed token cannot be re-minted " +
"automatically. Centralized healthcheck config fetch and relay notifications stay " +
"unavailable until the server-side row is purged and recreated (automatic once the " +
"row is purge-eligible) or cleared by an operator.")
}
return false, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Provisioned secret remains unrecoverable

When the local relay secret is missing while the server retains a confirmed registration, this branch logs a warning and returns without restoring credentials or initiating recovery. Each daemon retry receives already_provisioned and reloads the still-empty secret, leaving centralized healthchecks and relay notifications unavailable until a server-side purge or operator intervention.

Fix in Claude Code Fix in Cursor Fix in Codex

@tis24dev
tis24dev merged commit 30ae4f7 into main Jul 24, 2026
21 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 24, 2026
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