Skip to content

fix(cli): regenerate compose and re-exec binary on update#576

Merged
Aureliolo merged 10 commits intomainfrom
fix/cli-update-compose-refresh
Mar 19, 2026
Merged

fix(cli): regenerate compose and re-exec binary on update#576
Aureliolo merged 10 commits intomainfrom
fix/cli-update-compose-refresh

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • synthorg update now re-execs the new binary after CLI replacement so compose refresh and image pull use the new embedded template
  • New refreshCompose step diffs on-disk compose.yml against the current template, shows changes, and asks the user to approve before writing
  • JWT secrets are redacted in diff output to prevent credential exposure in terminal scrollback or CI logs
  • Fixes the root cause of backend crash after v0.3.6 upgrade (missing SYNTHORG_LOG_DIR in compose because the template changed but compose was never regenerated)

Changes

  • cli/cmd/update.go: Add reexecUpdate (re-exec after binary replacement with symlink resolution), refreshCompose (template diff + approval), lineDiff (bag-based diff with secret redaction), redactSecret helper. Refactor updateCLI to return error only (was (string, error) with unused string). Extract loadAndGenerate + applyComposeDiff to keep functions under 50 lines. Remove cliVersion parameter threading (now read from version.Version via ParamsFromState).
  • cli/cmd/start.go: Remove cliVersion parameter from writeDigestPinnedCompose (uses ParamsFromState which already reads version.Version). Drop unused version import.
  • cli/cmd/update_test.go: Table-driven TestLineDiff (6 cases including trailing newline), TestRedactSecret (3 cases), TestErrReexec_Identity (errors.Is contract).
  • docs/design/operations.md: Update CLI tool description to mention re-exec and compose refresh.
  • docs/user_guide.md: Document compose template refresh behavior during update.

Test plan

  • go build ./... clean
  • go vet ./... clean
  • golangci-lint run clean
  • go test ./... all pass
  • Pre-commit hooks pass (gitleaks, golangci-lint, go vet, commitizen)
  • Pre-push hooks pass (golangci-lint, go vet, go test)

Review coverage

Pre-reviewed by 4 agents (go-reviewer, go-security-reviewer, go-conventions-enforcer, docs-consistency), 15 findings addressed:

  • Secret redaction in compose diff output (security)
  • ErrNotExist-only error handling instead of swallowing all ReadFile errors
  • Symlink resolution in re-exec path (consistent with uninstall.go)
  • Dead code removal (unused oldSet allocation, unused string return)
  • Function size compliance (extracted helpers to stay under 50 lines)
  • Parameter rename (new -> updated to avoid shadowing built-in)
  • Table-driven tests with trailing-newline and redaction coverage
  • Documentation updates for new update behavior

🤖 Generated with Claude Code

Aureliolo and others added 2 commits March 19, 2026 10:45
The `synthorg update` command updated the CLI binary and pulled new
container images, but never regenerated compose.yml from the updated
template. This meant new env vars (like SYNTHORG_LOG_DIR), hardening
changes, and service config additions were silently missed -- users
had to manually run `synthorg init` to pick them up.

Two fixes:
- After CLI binary replacement, re-exec the new binary so compose
  refresh and image pull use the new embedded template
- Add refreshCompose step that diffs the on-disk compose against a
  fresh render from the template, shows the diff, and asks the user
  to approve before writing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, docs

Pre-reviewed by 4 agents, 15 findings addressed:
- Redact JWT secrets in compose diff output (security)
- Only ignore ErrNotExist when reading compose, propagate other errors
- Resolve symlinks in reexecUpdate (consistent with uninstall.go)
- Change updateCLI signature to return error only (string was unused)
- Extract loadAndGenerate + applyComposeDiff to keep functions under 50 lines
- Remove dead oldSet allocation in lineDiff
- Rename 'new' parameter to 'updated' (avoids shadowing built-in)
- Add secret redaction with secretKeyPattern regex
- Convert lineDiff tests to table-driven pattern
- Add trailing-newline and secret-redaction test cases
- Improve errReexec test to verify errors.Is identity
- Update operations.md and user_guide.md for new update behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the synthorg update command by making it more robust and user-friendly. The primary goal is to ensure that the CLI's embedded Docker Compose templates are always up-to-date and applied correctly, preventing configuration-related issues during upgrades. It achieves this by introducing a re-execution mechanism for the CLI binary itself, an interactive process for updating the compose.yml file with user approval, and a security measure to redact sensitive information from diff outputs.

Highlights

  • CLI Self-Update Process: The synthorg update command now re-executes the newly downloaded binary after replacement. This ensures that subsequent steps, such as compose refresh and image pulling, utilize the logic and embedded templates of the updated CLI version.
  • Compose File Regeneration and Approval: A new refreshCompose step has been introduced. It compares the on-disk compose.yml with the current embedded template, displays any differences to the user, and requires explicit approval before writing the updated compose file. This addresses issues where template changes (e.g., new environment variables like SYNTHORG_LOG_DIR) were not automatically applied, leading to potential backend crashes.
  • Secret Redaction in Diff Output: To enhance security, sensitive information such as JWT secrets is now automatically redacted in the diff output when compose.yml changes are displayed. This prevents credentials from being exposed in terminal scrollback or CI logs.
  • Code Refactoring and Testing: The updateCLI function was refactored to simplify its return type and signal re-execution. New helper functions (reexecUpdate, refreshCompose, loadAndGenerate, applyComposeDiff, lineDiff, redactSecret) were extracted to improve modularity and maintain function size. Comprehensive table-driven tests were added for the new diffing and redaction logic.
  • Documentation Updates: The design and user guide documentation have been updated to reflect the new CLI update behavior, including the automatic re-execution and the interactive compose template refresh process.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 09:57 — with GitHub Actions Inactive
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cfac6c80-2000-4921-8f7a-6b5e37de89e0

📥 Commits

Reviewing files that changed from the base of the PR and between 52e8c05 and 310c564.

📒 Files selected for processing (1)
  • cli/cmd/update_test.go
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: CLI Test (macos-latest)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use Cobra framework for CLI commands in the Go CLI binary
Use charmbracelet/lipgloss for styled CLI output and charmbracelet/huh for interactive forms in the Go CLI
Run golangci-lint, go vet, and go test for linting and testing all Go CLI code

Files:

  • cli/cmd/update_test.go
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:29:37.522Z
Learning: Applies to cli/**/*.{sh,ps1} : Install scripts (install.sh, install.ps1) should provide cross-platform CLI installation with automated self-update capability
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.
📚 Learning: 2026-03-19T11:29:37.522Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:29:37.522Z
Learning: Applies to cli/**/*.go : Run golangci-lint, go vet, and go test for linting and testing all Go CLI code

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).

Applied to files:

  • cli/cmd/update_test.go
🧬 Code graph analysis (1)
cli/cmd/update_test.go (1)
cli/internal/config/state.go (1)
  • State (16-28)
🔇 Additional comments (8)
cli/cmd/update_test.go (8)

3-13: LGTM!

Imports are appropriate for the test functionality - standard library for testing utilities and the internal config package for State struct usage.


39-111: LGTM!

Comprehensive table-driven tests covering identical input, additions, removals, changes, and trailing newline edge cases. The asymmetric "trailing newline removed" case has been added for complete coverage.


113-122: LGTM!

Fuzz testing ensures lineDiff handles arbitrary input without panicking - important for security since this processes user-modified compose files.


124-220: LGTM!

Thorough coverage of secret redaction patterns including the edge cases (empty values, different quoting styles, substring matching, whitespace variations, inline comments). The explicit test for substring matching at lines 177-180 documents the intentional over-redaction behavior for security.


222-236: LGTM!

Clean verification of the errReexec sentinel's identity semantics - critical for the re-exec flow to correctly detect when the CLI should re-execute after self-update.


238-248: LGTM!

Correctly tests the first-run scenario where no compose.yml exists yet - the function should return nil results without error.


250-280: LGTM!

Well-structured permission error test with all previously requested improvements:

  • Skips on Windows (unreliable permission behavior)
  • Probes environment to skip when mode bits are bypassed (root/containers)
  • Restores permissions before assertions to ensure cleanup succeeds even on test failure

282-410: LGTM!

Comprehensive test coverage for patchComposeImageRefs:

  • Lines 282-338: Verifies digest pin updates while preserving non-image config and CLI version comment
  • Lines 340-369: Validates tag fallback when digest pinning is disabled
  • Lines 371-389: Ensures error on non-SynthOrg compose files
  • Lines 391-410: Validates required service detection

The asymmetry between tests (sandbox present in main test but absent in TagFallback) correctly reflects that sandbox is optional based on config.State.Sandbox.


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Update now auto-relaunches the newer CLI binary and preserves exit codes from the re-launched process.
    • Update can preserve an existing compose file and, when chosen, patches only image references instead of regenerating the whole file.
    • Compose refresh shows a redacted diff for user approval before applying changes.
  • Bug Fixes

    • More robust error handling when reading the compose file at startup and during updates.
  • Documentation

    • Guides updated to describe re-exec behavior and compose-diff approval.

Walkthrough

Adds CLI self-update with automatic re-exec, interactive compose-template refresh showing a redacted diff for user approval, optional compose-preserving image updates that patch only image: lines, and removes CLI-version propagation when writing digest-pinned compose files.

Changes

Cohort / File(s) Summary
Compose generation
cli/cmd/start.go
Removed cliVersion parameter from writeDigestPinnedCompose and stopped setting params.CLIVersion; digest-pinned compose now derives from compose.ParamsFromState(state) + DigestPins. Tightened os.Stat error handling for safeDir/compose.yml and normalized missing-file delimiter.
CLI update flow & re-exec
cli/cmd/update.go, cli/main.go
Reworked update flow: updateCLI signals replacement via errReexec; added reexecUpdate, exported ChildExitError/ChildExitCode, and main now exits with child exit codes when present. runUpdate re-execs when replacement occurs.
Interactive compose refresh
cli/cmd/update.go (helpers)
Added loadAndGenerate, refreshCompose, applyComposeDiff, lineDiff, redactSecret, and interactive diff+apply logic that shows a redacted diff and atomically writes updated compose.yml on approval.
Image update / compose patching
cli/cmd/update.go (helpers)
Image update functions now accept state and preserveCompose; added writeOrPatchCompose and patchComposeImageRefs to either regenerate a full digest-pinned compose or patch image: lines for SynthOrg services, validating required services and atomically writing results.
Tests
cli/cmd/update_test.go
Added unit and fuzz tests for lineDiff, redactSecret, errReexec identity, loadAndGenerate error cases, and patchComposeImageRefs behaviors (digest pinning, tag fallback, no-match/missing-service error cases).
Docs
docs/design/operations.md, docs/user_guide.md
Documented automatic re-exec after binary replacement and compose-template refresh with a redacted diff and user approval for synthorg update.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "CLI Process"
    participant OS as "OS / Process Launcher"
    participant State as "Persisted State"
    participant Compose as "Compose Generator"
    participant Images as "Image Updater"

    User->>CLI: run "synthorg update"
    CLI->>CLI: updateCLI()
    alt binary replaced (errReexec)
        CLI->>OS: reexecUpdate() (spawn updated exe with flags)
        OS-->>CLI: new CLI process starts
    end
    CLI->>State: load persisted state
    CLI->>Compose: loadAndGenerate(state, verifiedDigests)
    Compose-->>CLI: generated compose bytes
    CLI->>CLI: lineDiff + redactSecret -> prompt user
    alt user approves
        CLI->>Compose: atomic write new compose.yml
    else user declines
        CLI->>CLI: set preserveCompose = true
    end
    CLI->>Images: updateContainerImages(state, preserveCompose)
    Images-->>CLI: image updates complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: re-exec binary and regenerate compose on update, which aligns with the core objectives of the PR.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering the main features (re-exec, compose refresh, secret redaction) and implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/cli-update-compose-refresh
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/cli-update-compose-refresh
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust self-update mechanism for the CLI, including re-executing the new binary and refreshing the Docker Compose configuration. The changes are well-structured, with clear separation of concerns into new functions. The addition of secret redaction in the compose diff is a great security improvement. The new logic is also well-covered by unit tests and documentation updates. I have one minor suggestion to improve error handling for even greater robustness.

Comment on lines +124 to +126
if resolved, resolveErr := filepath.EvalSymlinks(execPath); resolveErr == nil {
execPath = resolved
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The error from filepath.EvalSymlinks is currently ignored. While it's unlikely to fail if the self-update process just succeeded, it's better practice to handle or at least log the error for robustness and easier debugging in case of unexpected filesystem issues or race conditions. This would provide more insight if the re-exec process fails to launch the correct binary.

Suggested change
if resolved, resolveErr := filepath.EvalSymlinks(execPath); resolveErr == nil {
execPath = resolved
}
if resolved, err := filepath.EvalSymlinks(execPath); err == nil {
execPath = resolved
} else {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Warning: could not resolve executable symlink: %v\n", err)
}

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/update_test.go`:
- Around line 33-99: Add a fuzz test for lineDiff named FuzzLineDiff that uses
testing.F: register representative seed pairs with f.Add (e.g.,
"line1\nline2","line1\nline3" and "" , "new content") and call f.Fuzz with a
func(t *testing.T, old, updated string) that simply invokes lineDiff(old,
updated) to ensure it never panics on arbitrary inputs; place the new test
alongside TestLineDiff and import testing if needed.

In `@cli/cmd/update.go`:
- Around line 216-218: The secretKeyPattern used by lineDiff currently only
matches SYNTHORG_JWT_SECRET, JWT_SECRET, and SECRET_KEY; expand it to
future-proof redaction by adding common sensitive keys such as API_KEY,
PASSWORD, TOKEN (and any other env names you expect) and ensure the regex keeps
case-insensitive and leading whitespace handling; update the var
secretKeyPattern = regexp.MustCompile(...) accordingly and keep lineDiff usage
unchanged so new keys are redacted without further code changes.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ecf8cc8f-4bd3-4498-913b-afb568ff1317

📥 Commits

Reviewing files that changed from the base of the PR and between a1bedb8 and a7dfb15.

📒 Files selected for processing (5)
  • cli/cmd/start.go
  • cli/cmd/update.go
  • cli/cmd/update_test.go
  • docs/design/operations.md
  • docs/user_guide.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CLI Test (windows-latest)
🧰 Additional context used
📓 Path-based instructions (2)
docs/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Always read the relevant docs/design/ page before implementing any feature or planning any issue. The design spec is the starting point for architecture, data models, and behavior.

Files:

  • docs/user_guide.md
  • docs/design/operations.md
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use native Go testing with testing.F fuzz functions (Fuzz*) for fuzz testing.
Lint Go code with golangci-lint and go vet. Run tests with -race flag to detect race conditions.
Exception to the 'never use cd' rule: Go commands require cd cli because the Go module is in cli/ — Go tooling requires working directory to be the module root.

Files:

  • cli/cmd/start.go
  • cli/cmd/update_test.go
  • cli/cmd/update.go
🧠 Learnings (8)
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to docker/compose.yml : Configure Docker Compose with backend, web, and sandbox services. All environment variables defined in `docker/.env` (generate from `docker/.env.example`).

Applied to files:

  • docs/user_guide.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • docs/user_guide.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • docs/design/operations.md
  • cli/cmd/start.go
  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • docs/design/operations.md
  • cli/cmd/start.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: CLI workflow (`.github/workflows/cli.yml`) runs Go lint (golangci-lint + go vet) + test (race, coverage) + build (cross-compile matrix) + vulnerability check (govulncheck) + fuzz testing. Cross-compiles for linux/darwin/windows × amd64/arm64. GoReleaser release on v* tags with cosign keyless signing and SLSA L3 attestations.

Applied to files:

  • docs/design/operations.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Shell commands: for Go CLI work, cd cli is an exception because Go tooling requires working directory to be the module root. Go commands require `cd cli` for other work, never use `cd`.

Applied to files:

  • docs/design/operations.md
  • cli/cmd/update.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Use native Go testing with `testing.F` fuzz functions (`Fuzz*`) for fuzz testing.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Lint Go code with `golangci-lint` and `go vet`. Run tests with `-race` flag to detect race conditions.

Applied to files:

  • cli/cmd/update_test.go
🧬 Code graph analysis (2)
cli/cmd/start.go (1)
cli/internal/config/state.go (1)
  • State (16-28)
cli/cmd/update.go (6)
cli/internal/version/version.go (2)
  • Version (9-9)
  • RepoURL (5-5)
cli/internal/selfupdate/updater.go (3)
  • Check (67-69)
  • Download (195-230)
  • Replace (233-239)
cli/internal/config/state.go (2)
  • Load (53-91)
  • State (16-28)
cli/internal/compose/generate.go (2)
  • ParamsFromState (49-62)
  • Generate (66-85)
cli/internal/docker/client.go (1)
  • Info (22-29)
cli/internal/ui/ui.go (1)
  • UI (44-53)
🪛 GitHub Check: CodeQL
cli/cmd/update.go

[failure] 174-174: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

🔇 Additional comments (16)
docs/design/operations.md (1)

974-977: LGTM!

Documentation accurately reflects the new update command behavior: automatic re-exec after binary replacement and compose template refresh with diff approval.

docs/user_guide.md (1)

27-27: LGTM!

Clear user-facing documentation explaining the re-launch behavior after binary replacement and the compose diff approval flow.

cli/cmd/start.go (2)

123-125: LGTM!

The removal of the cliVersion parameter is correct since compose.ParamsFromState already populates CLIVersion from version.Version internally.


139-149: LGTM!

Function signature simplification is well-documented and the implementation correctly delegates version handling to compose.ParamsFromState.

cli/cmd/update_test.go (3)

33-99: LGTM!

Comprehensive table-driven tests for lineDiff covering identical inputs, additions, removals, changes, and trailing newline edge cases.


101-131: LGTM!

Good coverage for redactSecret including case-insensitive matching and ensuring non-secret lines remain unchanged.


133-141: LGTM!

Sentinel error identity test correctly verifies errors.Is semantics for the re-exec flow.

cli/cmd/update.go (9)

37-58: LGTM!

Clean implementation using a sentinel error for re-exec control flow. The errors.Is check is idiomatic and the flow is well-structured.


60-109: LGTM!

Clean refactor removing version propagation. Graceful handling of check failures with warnings, and clear signaling via errReexec after successful binary replacement.


111-133: LGTM!

Proper re-exec implementation with symlink resolution matching selfupdate.Replace behavior. Context and stdio are correctly propagated.


191-214: LGTM!

Clear user experience with diff display, confirmation prompt, and helpful message when changes are skipped.


216-260: LGTM!

The bag-based diff approach is appropriate for YAML files where line order often doesn't affect semantics. Secret redaction is correctly applied to all output lines.

Minor UX note: The diff shows isolated added/removed lines without surrounding context, which may be harder to interpret for large files. Consider this a potential future enhancement if user feedback indicates confusion.


310-356: LGTM!

Clean implementation with proper version comparison to skip unnecessary updates, graceful Docker unavailability handling, and clear update flow.


377-420: LGTM!

Robust rollback mechanism for compose.yml changes. The backup/restore pattern ensures consistency on failure.


424-461: LGTM!

Clean separation of verification logic with proper handling of --skip-verify flag. The 120-second timeout on OCI registry calls prevents indefinite hangs.


171-189: No action required — path traversal protection is properly implemented.

safeStateDir calls config.SecurePath, which validates the path by calling filepath.Clean() and checking filepath.IsAbs(). The SecurePath function is explicitly documented as satisfying CodeQL's go/path-injection check. The composed path filepath.Join(safeDir, "compose.yml") is safe since the directory is validated before the hardcoded filename is appended.

Pre-reviewed by 4 local agents + CodeRabbit + Gemini + CodeQL, 12 findings:
- Eliminate double config.Load TOCTOU: load state once in runUpdate, thread
  through refreshCompose and updateContainerImages
- Reconstruct re-exec args explicitly instead of forwarding raw os.Args
  (prevents silent --skip-verify propagation)
- Broaden secretKeyPattern to cover TOKEN, PASSWORD, API_KEY, CREDENTIALS
- Use regex submatch position in redactSecret instead of strings.Index
- Fix os.Stat error handling in start.go (was ignoring non-ErrNotExist)
- Replace em-dash with ASCII dashes in start.go error message
- Log EvalSymlinks failure to stderr in reexecUpdate
- Add FuzzLineDiff fuzz test
- Add TestLoadAndGenerate_NoCompose and _PermissionError tests
- Improve TestErrReexec_Identity to test wrapping survival
- Expand TestRedactSecret with TOKEN/PASSWORD/API_KEY/CREDENTIALS cases
- Update Cobra Short help string to mention compose refresh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 10:18 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/update_test.go`:
- Around line 198-215: The test TestLoadAndGenerate_PermissionError should
restore file permissions before making assertions to avoid masking failures:
after invoking loadAndGenerate(composePath, config.State{}) and before checking
err or using the file, call os.Chmod(composePath, 0o600) (failing the test with
t.Fatalf if that chmod returns an error) so the file is accessible for
subsequent assertions; keep the existing t.Cleanup(func() { _ =
os.Chmod(composePath, 0o600) }) for best-effort cleanup but perform the
guaranteed chmod immediately before the error checks that reference composePath
or the error content.

In `@cli/cmd/update.go`:
- Around line 126-157: reexecUpdate should preserve the re-launched process exit
code instead of always returning a generic error; after c.Run(), detect if err
is an *exec.ExitError (use errors.As) and extract the exit code via
ExitError.ExitCode(), then return a small typed error that carries that code
(e.g., define childExitError{code int} with Error() string) so the program
entrypoint can inspect that type and call os.Exit(code); otherwise return the
original err. Ensure references: reexecUpdate, exec.CommandContext / c.Run,
exec.ExitError, ExitCode(), and the new childExitError type.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b5c364d-c15e-4c62-b8f4-e7b4496a516f

📥 Commits

Reviewing files that changed from the base of the PR and between a7dfb15 and 2e91652.

📒 Files selected for processing (3)
  • cli/cmd/start.go
  • cli/cmd/update.go
  • cli/cmd/update_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: CLI Test (windows-latest)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use native Go testing with testing.F fuzz functions (Fuzz*) for fuzz testing.
Lint Go code with golangci-lint and go vet. Run tests with -race flag to detect race conditions.
Exception to the 'never use cd' rule: Go commands require cd cli because the Go module is in cli/ — Go tooling requires working directory to be the module root.

Files:

  • cli/cmd/start.go
  • cli/cmd/update_test.go
  • cli/cmd/update.go
🧠 Learnings (5)
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/start.go
  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • cli/cmd/start.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Use native Go testing with `testing.F` fuzz functions (`Fuzz*`) for fuzz testing.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Lint Go code with `golangci-lint` and `go vet`. Run tests with `-race` flag to detect race conditions.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Shell commands: for Go CLI work, cd cli is an exception because Go tooling requires working directory to be the module root. Go commands require `cd cli` for other work, never use `cd`.

Applied to files:

  • cli/cmd/update.go
🧬 Code graph analysis (2)
cli/cmd/start.go (1)
cli/internal/config/state.go (1)
  • State (16-28)
cli/cmd/update.go (5)
cli/internal/config/state.go (2)
  • Load (53-91)
  • State (16-28)
cli/internal/version/version.go (1)
  • Version (9-9)
cli/internal/selfupdate/updater.go (3)
  • Check (67-69)
  • Download (195-230)
  • Replace (233-239)
cli/internal/compose/generate.go (2)
  • ParamsFromState (49-62)
  • Generate (66-85)
cli/internal/docker/client.go (1)
  • Info (22-29)
🔇 Additional comments (15)
cli/cmd/start.go (2)

45-50: LGTM! Proper error differentiation for os.Stat.

The change correctly distinguishes between a missing compose.yml (user-actionable: run init) and other Stat errors (permission issues, broken symlinks, etc.) that should surface the underlying cause.


126-127: LGTM! Simplified signature removes redundant version threading.

Removing the cliVersion parameter is correct since compose.ParamsFromState(state) already populates CLIVersion from version.Version (as shown in cli/internal/compose/generate.go lines 48-61). This eliminates redundant parameter passing and ensures consistency.

Also applies to: 142-144

cli/cmd/update_test.go (4)

39-105: LGTM! Comprehensive table-driven tests for lineDiff.

Good coverage of edge cases: identical input, added/removed/changed lines, and trailing newline handling. The use of wantContains/wantAbsent assertions appropriately validates the bag-based diff behavior without being overly strict about formatting.


107-116: LGTM! Fuzz test follows coding guidelines.

The FuzzLineDiff function correctly uses testing.F with representative seed values and validates that lineDiff doesn't panic on arbitrary input, per the coding guidelines for cli/**/*.go.


118-168: LGTM! Thorough secret redaction test coverage.

Tests cover all documented secret patterns (SECRET, PASSWORD, TOKEN, API_KEY, CREDENTIALS) including case-insensitivity and non-secret pass-through behavior.


170-184: LGTM! Sentinel error identity tests are well-structured.

The tests correctly verify errReexec behavior with errors.Is for both direct comparison and wrapped errors, ensuring the sentinel pattern works as expected for the re-exec control flow.

cli/cmd/update.go (9)

37-61: LGTM! Well-structured update flow with single config load.

The refactored runUpdate correctly:

  1. Handles errReexec to re-launch after binary replacement
  2. Loads config once and threads state through subsequent steps (avoiding TOCTOU)
  3. Separates compose refresh from container image updates

The re-exec won't infinite loop because the new binary's updateCLI will see "CLI is up to date" and return nil instead of errReexec.


63-117: LGTM! Clean sentinel error pattern for re-exec signaling.

Using errReexec as a sentinel is idiomatic Go. The flow correctly returns nil for all non-update paths (no update available, user declined, dev build warning) and only returns errReexec after successful Replace.


141-150: Good defensive arg reconstruction.

Explicitly reconstructing args from known flags (--data-dir, --skip-verify) instead of forwarding os.Args prevents unexpected flag propagation. The warning for --skip-verify is appropriate given its security implications.


188-206: Path input is validated upstream; GHAS finding is a false positive.

The composePath at line 191 is constructed from safeDir (validated via safeStateDirconfig.SecurePath) and a constant filename "compose.yml". The GHAS alert about "uncontrolled data used in path expression" is mitigated by the upstream validation in config.SecurePath.


247-281: Bag-based diff is appropriate for compose approval UX.

The multiset approach correctly identifies added/removed lines without being sensitive to reordering. This is a reasonable trade-off for user-facing diffs where structural changes matter more than line positions.

One edge case: if the same line appears multiple times, only the net difference is shown. This is acceptable for compose files where duplicate lines are rare.


286-293: LGTM! Redaction uses submatch position correctly.

Using loc[1] (end of match including colon) ensures the key name is preserved while the value is replaced. This provides useful context in the diff while protecting secrets.


332-372: LGTM! Simplified signature aligns with compose generation changes.

Removing effectiveVersion parameter is consistent with the changes in start.go and writeDigestPinnedCompose. The version is now derived from version.Version via compose.ParamsFromState.


393-436: LGTM! Consistent signature updates throughout the update flow.

pullAndPersist and verifyAndPinForUpdate signatures are updated consistently with the removal of explicit version threading. The rollback logic in pullAndPersist correctly handles both existing and non-existing compose files.

Also applies to: 440-477


237-239: The regex intentionally uses broad patterns to avoid missing secrets—false positives are acceptable here.

The pattern \w*(SECRET|...)\w* will match variations like SECRETKEEPER or NOTSECRETLY alongside legitimate secrets like SYNTHORG_JWT_SECRET. This is by design: the goal is to redact sensitive values in diff output before displaying in logs, so over-redaction is safer than missing an actual secret. Tests confirm all common secret patterns (JWT_SECRET, PASSWORD, TOKEN, API_KEY, CREDENTIALS) are correctly identified, and the broad matching prevents false negatives at the acceptable cost of potential false positives.

Comment on lines +126 to +157
func reexecUpdate(cmd *cobra.Command) error {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Re-launching updated CLI to continue...")

execPath, err := os.Executable()
if err != nil {
return fmt.Errorf("finding executable path: %w", err)
}
// Resolve symlinks to match the pattern in uninstall.go --
// selfupdate.Replace writes to the resolved path.
if resolved, resolveErr := filepath.EvalSymlinks(execPath); resolveErr == nil {
execPath = resolved
} else {
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Warning: could not resolve executable symlink: %v\n", resolveErr)
}

// Reconstruct args from known flags instead of forwarding os.Args
// to avoid silently propagating unexpected flags.
reArgs := []string{"update"}
if dataDir != "" {
reArgs = append(reArgs, "--data-dir", dataDir)
}
if skipVerify {
reArgs = append(reArgs, "--skip-verify")
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), "Warning: --skip-verify is being carried forward to the re-launched CLI.")
}

c := exec.CommandContext(cmd.Context(), execPath, reArgs...)
c.Stdin = os.Stdin
c.Stdout = cmd.OutOrStdout()
c.Stderr = cmd.ErrOrStderr()
return c.Run()
}
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 | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if other commands in this codebase propagate exit codes from child processes
rg -n 'exec\.CommandContext|exec\.Command' cli/cmd/*.go -A5 | head -60

Repository: Aureliolo/synthorg

Length of output: 647


🏁 Script executed:

# Find where reexecUpdate is called and understand the Cobra command setup
rg -n 'reexecUpdate' cli/cmd/*.go -B2 -A2

Repository: Aureliolo/synthorg

Length of output: 1087


🏁 Script executed:

# Check the main/root command setup and error handling
fd -t f -e go cli | xargs grep -l 'main\|cobra.Root' | head -5

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Look at how errors from Run/Execute are handled
rg -n 'cmd.Execute\|cmd.Run' cli/*.go -B2 -A2

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# Find main entry point
fd -t f -e go cli | xargs grep -l 'func main'

Repository: Aureliolo/synthorg

Length of output: 46


🏁 Script executed:

# Look for root command setup in CLI
fd -t f -e go cli/cmd | xargs grep -l 'NewCmdRoot\|rootCmd\|RootCmd' | head -3

Repository: Aureliolo/synthorg

Length of output: 395


🏁 Script executed:

# Check how the update command is added to root command
rg -n 'updateCmd\|AddCommand.*update' cli/cmd/*.go

Repository: Aureliolo/synthorg

Length of output: 44


🏁 Script executed:

# List Go files in cli directory
find cli -maxdepth 2 -name '*.go' -type f | head -20

Repository: Aureliolo/synthorg

Length of output: 457


🏁 Script executed:

# Look for main function and root command setup
rg -n 'func main|RootCmd|rootCmd' cli/ -A 3

Repository: Aureliolo/synthorg

Length of output: 9619


Verify child process exit code propagation.

When c.Run() returns an error from the re-executed binary, the error is propagated to the caller. However, the main entry point (cli/main.go) converts any error to os.Exit(1), which masks the child's actual exit code. This loses exit code fidelity for CI systems expecting specific codes.

Consider extracting and returning the child's exit code:

♻️ Proposed enhancement for exit code fidelity
+	if err := c.Run(); err != nil {
+		var exitErr *exec.ExitError
+		if errors.As(err, &exitErr) {
+			os.Exit(exitErr.ExitCode())
+		}
+		return err
+	}
+	return nil
-	return c.Run()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/update.go` around lines 126 - 157, reexecUpdate should preserve the
re-launched process exit code instead of always returning a generic error; after
c.Run(), detect if err is an *exec.ExitError (use errors.As) and extract the
exit code via ExitError.ExitCode(), then return a small typed error that carries
that code (e.g., define childExitError{code int} with Error() string) so the
program entrypoint can inspect that type and call os.Exit(code); otherwise
return the original err. Ensure references: reexecUpdate, exec.CommandContext /
c.Run, exec.ExitError, ExitCode(), and the new childExitError type.

- reexecUpdate now returns ChildExitError with the child's exit code
  instead of a generic exec.ExitError, so main.go can os.Exit(code)
  rather than always exiting 1
- TestLoadAndGenerate_PermissionError restores file permissions before
  assertions to avoid masking failures on cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/update.go`:
- Around line 56-60: The current flow lets updateContainerImages run even when
applyComposeDiff was declined, which bypasses the compose-approval prompt and
causes writeDigestPinnedCompose to rewrite compose.yml; fix by short-circuiting
when the user declines (or by providing an explicit result flag) so
updateContainerImages is not invoked after a declined applyComposeDiff.
Concretely: have refreshCompose/applyComposeDiff return a non-nil sentinel (or
error) or a boolean (e.g., "applied bool" or "approved bool") and check that
value before calling updateContainerImages in the caller (the block around
refreshCompose -> updateContainerImages), or alternatively change
verifyAndPinForUpdate/writeDigestPinnedCompose to support an
"update-only-digests-in-place" path that modifies the existing compose file
instead of regenerating the template when applyComposeDiff indicates decline;
reference functions applyComposeDiff, refreshCompose, updateContainerImages,
verifyAndPinForUpdate, and writeDigestPinnedCompose when making the change.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7184ed0c-8044-4188-b5da-3d384b953d20

📥 Commits

Reviewing files that changed from the base of the PR and between 2e91652 and 2fda6d9.

📒 Files selected for processing (3)
  • cli/cmd/update.go
  • cli/cmd/update_test.go
  • cli/main.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use native Go testing with testing.F fuzz functions (Fuzz*) for fuzz testing.
Lint Go code with golangci-lint and go vet. Run tests with -race flag to detect race conditions.
Exception to the 'never use cd' rule: Go commands require cd cli because the Go module is in cli/ — Go tooling requires working directory to be the module root.

Files:

  • cli/main.go
  • cli/cmd/update_test.go
  • cli/cmd/update.go
🧠 Learnings (6)
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/main.go
  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Use native Go testing with `testing.F` fuzz functions (`Fuzz*`) for fuzz testing.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: CLI workflow (`.github/workflows/cli.yml`) runs Go lint (golangci-lint + go vet) + test (race, coverage) + build (cross-compile matrix) + vulnerability check (govulncheck) + fuzz testing. Cross-compiles for linux/darwin/windows × amd64/arm64. GoReleaser release on v* tags with cosign keyless signing and SLSA L3 attestations.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Lint Go code with `golangci-lint` and `go vet`. Run tests with `-race` flag to detect race conditions.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Shell commands: for Go CLI work, cd cli is an exception because Go tooling requires working directory to be the module root. Go commands require `cd cli` for other work, never use `cd`.

Applied to files:

  • cli/cmd/update.go
🧬 Code graph analysis (3)
cli/main.go (1)
cli/cmd/update.go (1)
  • ChildExitCode (132-138)
cli/cmd/update_test.go (1)
cli/internal/config/state.go (1)
  • State (16-28)
cli/cmd/update.go (2)
cli/internal/version/version.go (1)
  • Version (9-9)
cli/internal/compose/generate.go (2)
  • ParamsFromState (49-62)
  • Generate (66-85)
🔇 Additional comments (1)
cli/main.go (1)

12-16: Nice exit-code propagation.

This preserves the re-exec'd child's status for callers and CI instead of collapsing every failure to exit code 1.

When the user declines compose template changes during update,
writeDigestPinnedCompose would silently regenerate compose from the
new template anyway (applying the declined changes). Now:

- User declines compose refresh -> warned that new images may not work
  with old compose -> asked to confirm (default: no)
- If they insist: images update but compose.yml is preserved -- only
  image references (tag/digest) are patched via patchComposeImageRefs
- If they decline: image update skipped entirely

Also adds confirmUpdateWithDefault for prompts with configurable
default, and tests for patchComposeImageRefs (digest-pinned + tag
fallback scenarios).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 11:12 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/update_test.go`:
- Around line 204-222: The test sets composePath to 0o000 to simulate permission
denial but can be bypassed in root/container environments; after creating the
file (os.WriteFile) and before calling loadAndGenerate(composePath,
config.State{}), attempt to open/read the file (e.g., os.Open or os.ReadFile)
and if that read succeeds, call t.Skipf explaining the environment can bypass
mode bits so the test is not valid; keep the existing cleanup that restores
permissions via os.Chmod(composePath, 0o600) and then proceed with the original
assertions only when the read attempt fails.

In `@cli/cmd/update.go`:
- Around line 542-546: patchComposeImageRefs can return nil even when it made no
replacements (or only a subset), causing writeOrPatchCompose to treat
preserve-mode updates as successful while config.ImageTag/VerifiedDigests are
advanced; change patchComposeImageRefs to detect and return an error when zero
replacements occur or when not all expected service image refs were patched
(consider validating against state.Services or the digestPins keys), then update
writeOrPatchCompose to propagate that error (so preserve-mode fails fast) and
ensure the callers that call config.Save only do so when patchComposeImageRefs
succeeded; also apply the same validation/propagation to the other preserve-mode
call sites that currently assume a nil result (e.g., the second invocation
similar to writeOrPatchCompose) so compose.yml is never left unpatched while
metadata advances.
- Around line 66-78: The prompt text passed to confirmUpdateWithDefault is
misleading because even when preserving the compose file it may still rewrite
matched image: lines via patchComposeImageRefs when updateContainerImages is
called; either change the prompt string to clearly state that images in
compose.yml may be updated (e.g., "Compose file preserved except image:
references may be updated") or, if a true no-touch option is desired, skip
calling updateContainerImages when the preserve flag is set; update the
call-site using confirmUpdateWithDefault and the subsequent branch that calls
updateContainerImages(cmd, state, true) (and consider the forceImages flag) so
the behavior and message align.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc99e6fc-5620-472a-b13e-e06665053024

📥 Commits

Reviewing files that changed from the base of the PR and between 2fda6d9 and ab7ac48.

📒 Files selected for processing (2)
  • cli/cmd/update.go
  • cli/cmd/update_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use native Go testing with testing.F fuzz functions (Fuzz*) for fuzz testing.
Lint Go code with golangci-lint and go vet. Run tests with -race flag to detect race conditions.
Exception to the 'never use cd' rule: Go commands require cd cli because the Go module is in cli/ — Go tooling requires working directory to be the module root.

Files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Use native Go testing with `testing.F` fuzz functions (`Fuzz*`) for fuzz testing.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: CLI workflow (`.github/workflows/cli.yml`) runs Go lint (golangci-lint + go vet) + test (race, coverage) + build (cross-compile matrix) + vulnerability check (govulncheck) + fuzz testing. Cross-compiles for linux/darwin/windows × amd64/arm64. GoReleaser release on v* tags with cosign keyless signing and SLSA L3 attestations.

Applied to files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to cli/**/*.go : Lint Go code with `golangci-lint` and `go vet`. Run tests with `-race` flag to detect race conditions.

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-19T09:01:47.243Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T09:01:47.243Z
Learning: Applies to go.mod : Maintain Go 1.26+ requirement. Dependencies: Cobra (CLI framework), charmbracelet/huh and charmbracelet/lipgloss (UI), sigstore-go (code signing), go-containerregistry (container image verification), go-tuf (TUF client for Sigstore).

Applied to files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • cli/cmd/update.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Shell commands: for Go CLI work, cd cli is an exception because Go tooling requires working directory to be the module root. Go commands require `cd cli` for other work, never use `cd`.

Applied to files:

  • cli/cmd/update.go
🧬 Code graph analysis (1)
cli/cmd/update.go (4)
cli/internal/config/state.go (2)
  • Load (53-91)
  • State (16-28)
cli/internal/version/version.go (2)
  • Version (9-9)
  • RepoURL (5-5)
cli/internal/selfupdate/updater.go (3)
  • Check (67-69)
  • Download (195-230)
  • Replace (233-239)
cli/internal/compose/generate.go (2)
  • ParamsFromState (49-62)
  • Generate (66-85)
🪛 GitHub Check: CodeQL
cli/cmd/update.go

[failure] 560-560: Uncontrolled data used in path expression
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.
This path depends on a user-provided value.

- patchComposeImageRefs now errors when zero image refs found or
  required services (backend, web) are missing -- prevents config.Save
  from advancing state when compose is unpatched
- Fix misleading prompt: "will NOT be modified" -> clarifies that
  image references will be updated while template changes are preserved
- TestLoadAndGenerate_PermissionError: skip when environment bypasses
  file mode bits (root/container)
- Add TestPatchComposeImageRefs_NoMatchesError and _MissingRequiredService
- Fix TestPatchComposeImageRefs_TagFallback fixture (was missing web)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 11:25 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
cli/cmd/update.go (1)

540-598: ⚠️ Potential issue | 🟡 Minor

Add sandbox validation to patchComposeImageRefs when sandbox service is enabled.

When preserveCompose=true and state.Sandbox=true, digestPins includes a sandbox digest from verification. However, patchComposeImageRefs only validates that backend and web image references were patched—sandbox validation is skipped. If the compose file has a malformed or mirrored sandbox image reference that doesn't match the regex, the function succeeds while config.Save advances VerifiedDigests with the sandbox digest, leaving compose.yml pointing to an outdated sandbox image.

Pass state.Sandbox to patchComposeImageRefs and validate sandbox when enabled:

Proposed fix
-func patchComposeImageRefs(tag string, digestPins map[string]string, safeDir string) error {
+func patchComposeImageRefs(tag string, digestPins map[string]string, safeDir string, sandbox bool) error {
 	composePath := filepath.Join(safeDir, "compose.yml")
 	existing, err := os.ReadFile(composePath)
 	if err != nil {
 		return fmt.Errorf("reading compose for image patching: %w", err)
 	}

 	replaced := make(map[string]bool)
 	patched := imageLinePattern.ReplaceAllStringFunc(string(existing), func(match string) string {
 		sub := imageLinePattern.FindStringSubmatch(match)
 		if len(sub) < 3 {
 			return match
 		}
 		prefix := sub[1]
 		name := sub[2]
 		repo := "ghcr.io/aureliolo/synthorg-" + name
 		replaced[name] = true

 		if d, ok := digestPins[name]; ok && d != "" {
 			return prefix + repo + "@" + d
 		}
 		return prefix + repo + ":" + tag
 	})

 	if len(replaced) == 0 {
 		return fmt.Errorf("no synthorg image references found in %s -- compose may be manually edited; run 'synthorg init' to regenerate", composePath)
 	}

-	// At minimum, backend and web must be patched.
-	for _, required := range []string{"backend", "web"} {
+	// Required services must be patched.
+	required := []string{"backend", "web"}
+	if sandbox {
+		required = append(required, "sandbox")
+	}
+	for _, svc := range required {
-		if !replaced[required] {
-			return fmt.Errorf("image reference for %q not found in %s -- compose may be manually edited; run 'synthorg init' to regenerate", required, composePath)
+		if !replaced[svc] {
+			return fmt.Errorf("image reference for %q not found in %s -- compose may be manually edited; run 'synthorg init' to regenerate", svc, composePath)
 		}
 	}

 	return atomicWriteFile(composePath, []byte(patched), safeDir)
 }

Update the caller in writeOrPatchCompose:

 func writeOrPatchCompose(state config.State, digestPins map[string]string, safeDir string, preserveCompose bool) error {
 	if !preserveCompose {
 		return writeDigestPinnedCompose(state, digestPins, safeDir)
 	}
-	return patchComposeImageRefs(state.ImageTag, digestPins, safeDir)
+	return patchComposeImageRefs(state.ImageTag, digestPins, safeDir, state.Sandbox)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/update.go` around lines 540 - 598, The patch misses validating the
sandbox image when preserveCompose is true: update writeOrPatchCompose to pass
state.Sandbox into patchComposeImageRefs, change patchComposeImageRefs signature
to accept a sandboxEnabled bool, and inside patchComposeImageRefs add "sandbox"
to the list of required services (or conditionally check it) when sandboxEnabled
is true so that the function fails if a sandbox image reference isn't patched;
keep using imageLinePattern, digestPins and atomicWriteFile as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@cli/cmd/update.go`:
- Around line 540-598: The patch misses validating the sandbox image when
preserveCompose is true: update writeOrPatchCompose to pass state.Sandbox into
patchComposeImageRefs, change patchComposeImageRefs signature to accept a
sandboxEnabled bool, and inside patchComposeImageRefs add "sandbox" to the list
of required services (or conditionally check it) when sandboxEnabled is true so
that the function fails if a sandbox image reference isn't patched; keep using
imageLinePattern, digestPins and atomicWriteFile as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 61bfccef-9fc7-4ad3-bc8f-1784553ff48d

📥 Commits

Reviewing files that changed from the base of the PR and between ab7ac48 and 4a99af0.

📒 Files selected for processing (2)
  • cli/cmd/update.go
  • cli/cmd/update_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CLI Test (macos-latest)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use native testing.F fuzz functions (Fuzz*) for fuzz testing Go code
Run Go lint via golangci-lint run, vet via go vet, tests via go test ./..., and fuzz via go test -fuzz=FuzzTarget -fuzztime=30s
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in CLI code — use generic names: test-provider, test-small-001, etc.; vendor names only in third-party import paths

Files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.
📚 Learning: 2026-03-19T11:13:43.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:13:43.548Z
Learning: Applies to cli/**/*.go : Run Go lint via `golangci-lint run`, vet via `go vet`, tests via `go test ./...`, and fuzz via `go test -fuzz=FuzzTarget -fuzztime=30s`

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-19T11:13:43.548Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:13:43.548Z
Learning: Applies to cli/**/*.go : Use native `testing.F` fuzz functions (`Fuzz*`) for fuzz testing Go code

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • cli/cmd/update.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Shell commands: for Go CLI work, cd cli is an exception because Go tooling requires working directory to be the module root. Go commands require `cd cli` for other work, never use `cd`.

Applied to files:

  • cli/cmd/update.go
🧬 Code graph analysis (1)
cli/cmd/update.go (6)
cli/internal/config/state.go (2)
  • Load (53-91)
  • State (16-28)
cli/internal/version/version.go (2)
  • Version (9-9)
  • RepoURL (5-5)
cli/internal/selfupdate/updater.go (3)
  • Check (67-69)
  • Download (195-230)
  • Replace (233-239)
cli/internal/compose/generate.go (2)
  • ParamsFromState (49-62)
  • Generate (66-85)
cli/internal/docker/client.go (1)
  • Info (22-29)
cli/internal/ui/ui.go (1)
  • UI (44-53)
🔇 Additional comments (13)
cli/cmd/update_test.go (7)

39-105: LGTM! Comprehensive table-driven tests for lineDiff.

The tests cover key scenarios: identical inputs, added/removed/changed lines, and trailing newline handling. The structure with wantContains, wantAbsent, and wantEmpty fields allows precise assertions without brittle exact-match comparisons.


107-116: LGTM! Fuzz test compliant with coding guidelines.

The FuzzLineDiff function uses testing.F with representative seed inputs and verifies the function doesn't panic on arbitrary string pairs. As per coding guidelines: "Use native testing.F fuzz functions (Fuzz*) for fuzz testing Go code".


118-168: LGTM! Good coverage of secret key patterns.

Tests cover JWT_SECRET, TOKEN, PASSWORD, API_KEY, and CREDENTIALS variants with case-insensitive matching, matching the expanded secretKeyPattern regex.


170-184: LGTM! Proper sentinel error identity testing.

Tests verify errReexec works correctly with errors.Is both directly and when wrapped via %w.


198-228: LGTM! Robust permission test with environment bypass detection.

The test now properly:

  1. Skips on Windows where mode bits aren't enforced
  2. Probes to skip when running as root/in containers that bypass mode bits
  3. Restores permissions before assertions to ensure cleanup succeeds

230-286: LGTM! Thorough patchComposeImageRefs test.

Tests verify that:

  • All three services get digest-pinned updates
  • Non-image configuration lines are preserved
  • The CLI version comment is preserved (important for preserveCompose mode)

288-358: LGTM! Good error case coverage for patchComposeImageRefs.

Tests cover:

  • Tag fallback when digest pins are nil
  • Error when no synthorg image refs are found
  • Error when required services are missing
cli/cmd/update.go (6)

37-82: LGTM! Clear control flow with proper error handling.

The refactored runUpdate has a well-structured flow:

  1. CLI update with re-exec on binary replacement
  2. Single config load (avoiding TOCTOU)
  3. Compose refresh with user approval
  4. Graceful handling when user declines compose changes (warning + secondary confirmation for image-only updates)

The preserveCompose flag correctly propagates the user's decision through the image update path.


84-138: LGTM! Clean sentinel error pattern for re-exec signaling.

Returning errReexec to signal binary replacement is cleaner than returning a tuple or out-parameter. The caller can handle it specifically via errors.Is.


140-211: LGTM! Proper exit code propagation for re-exec.

ChildExitError and ChildExitCode provide a clean way for the entrypoint to propagate the child's exit code. The symlink resolution in reexecUpdate matches the pattern in selfupdate.Replace, ensuring the correct binary is executed.


213-286: LGTM! Compose refresh with atomic write and user approval.

The flow is correct:

  • loadAndGenerate gracefully handles missing compose (returns nil, nil, nil)
  • applyComposeDiff shows the diff and requires explicit approval
  • atomicWriteFile ensures the write is atomic

288-348: LGTM! Secure diff output with comprehensive secret redaction.

The secretKeyPattern covers common sensitive key naming conventions (SECRET, PASSWORD, TOKEN, API_KEY, CREDENTIALS) with case-insensitive matching. The bag-based diff approach is documented as intentional—reordered lines won't show as changes, which is acceptable for compose files where YAML key order typically doesn't matter semantically.


430-451: Clarify the auto-accept behavior for compose configuration changes in non-interactive mode.

The default-yes behavior differs inconsistently across call sites. Line 66 conservatively uses a false default when container images might be incompatible with the current compose (a risky fallback scenario), yet line 272 auto-accepts compose configuration changes in non-interactive mode despite showing a diff first. Similarly, line 114 (CLI update) and line 414 (normal image update) both use true defaults. Confirm whether auto-accepting compose template changes in CI/automation environments (line 272) is intentional, given the risk of unexpected configuration modifications.

patchComposeImageRefs now accepts sandboxEnabled and validates that the
sandbox image reference was patched when sandbox is active. Prevents
config.Save from advancing VerifiedDigests for sandbox while the
compose still references the old sandbox image.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 11:33 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/update_test.go`:
- Around line 39-105: Add a symmetric "trailing newline removed" test case to
TestLineDiff to mirror the existing "trailing newline added" case: in the tests
slice inside TestLineDiff add an entry where old has a trailing newline (e.g.,
"line1\nline2\n"), updated lacks it ("line1\nline2"), and assert that the diff
from lineDiff(old, updated) contains the expected removal marker (e.g., "- " or
"- <line>") similar to the other cases; place this new case alongside the other
test entries so it runs with the table-driven tests.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a6f06241-32ff-4d54-841a-204f70a31215

📥 Commits

Reviewing files that changed from the base of the PR and between 4a99af0 and e487997.

📒 Files selected for processing (2)
  • cli/cmd/update.go
  • cli/cmd/update_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: CLI Test (windows-latest)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use Cobra framework for CLI commands in the Go CLI binary
Use charmbracelet/lipgloss for styled CLI output and charmbracelet/huh for interactive forms in the Go CLI
Run golangci-lint, go vet, and go test for linting and testing all Go CLI code

Files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.
📚 Learning: 2026-03-19T11:29:37.522Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:29:37.522Z
Learning: Applies to cli/**/*.go : Run golangci-lint, go vet, and go test for linting and testing all Go CLI code

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/update_test.go
  • cli/cmd/update.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Fix everything valid — never skip when review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes). No deferring, no 'out of scope' skipping.

Applied to files:

  • cli/cmd/update.go
📚 Learning: 2026-03-19T11:29:37.522Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:29:37.522Z
Learning: Applies to cli/**/*.go : Use Cobra framework for CLI commands in the Go CLI binary

Applied to files:

  • cli/cmd/update.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Shell commands: for Go CLI work, cd cli is an exception because Go tooling requires working directory to be the module root. Go commands require `cd cli` for other work, never use `cd`.

Applied to files:

  • cli/cmd/update.go
🧬 Code graph analysis (1)
cli/cmd/update_test.go (1)
cli/internal/config/state.go (1)
  • State (16-28)
🔇 Additional comments (23)
cli/cmd/update_test.go (10)

3-13: LGTM!

Imports are appropriate for the test coverage being added. The standard library packages and internal config import are correctly scoped for the tests in this file.


107-116: LGTM!

Fuzz test properly validates that lineDiff doesn't panic on arbitrary inputs. The seed corpus covers relevant edge cases.


118-168: LGTM!

Comprehensive test coverage for redactSecret covering all secret key patterns including case insensitivity. The test cases align with the secretKeyPattern regex in update.go.


170-184: LGTM!

Proper verification of sentinel error identity and wrapping behavior. This ensures the re-exec flow correctly detects errReexec even when wrapped.


186-196: LGTM!

Correctly tests the "no existing compose" edge case. Uses t.TempDir() for automatic cleanup.


198-228: LGTM!

Well-structured permission error test with appropriate guards:

  • Windows skip for reliability
  • Root/container environment detection to avoid false failures
  • Permission restoration before assertions per past feedback

230-286: LGTM!

Comprehensive test of patchComposeImageRefs with digest pins. Validates image reference updates while preserving non-image configuration and the CLI version comment.


288-317: LGTM!

Correctly tests the tag-based fallback path when digest pins are unavailable. The sandboxEnabled=false matches the compose content having only backend and web services.


319-337: LGTM!

Validates the error case when no SynthOrg image references are found, ensuring users get clear guidance to run synthorg init.


339-358: LGTM!

Validates that missing required services (web in this case) are detected and reported with actionable error messaging.

cli/cmd/update.go (13)

3-25: LGTM!

Imports align with coding guidelines: Cobra for CLI commands and charmbracelet/huh for interactive forms.


37-82: LGTM!

Well-structured update flow:

  • Handles errReexec sentinel correctly for CLI self-update
  • Loads config once to avoid TOCTOU gaps
  • Provides clear user guidance when compose changes are declined
  • Past feedback about compose-approval bypass has been addressed

84-138: LGTM!

Clean implementation of the CLI self-update with errReexec sentinel pattern. The flow handles all cases (up-to-date, declined, error, success) appropriately.


140-159: LGTM!

ChildExitError and ChildExitCode properly enable exit code propagation from the re-exec'd child process, addressing CI exit code fidelity concerns from past feedback.


161-211: LGTM!

Robust re-exec implementation:

  • Resolves symlinks to match selfupdate.Replace behavior
  • Reconstructs args from known flags for safety
  • Warns about --skip-verify propagation
  • Properly extracts and wraps child exit codes

262-286: LGTM!

Clean implementation of the compose diff approval flow with atomic writes.


288-348: LGTM!

Secret redaction covers common sensitive key patterns. The bag-based diff is appropriate for compose files where line order is semantically irrelevant, and the limitation is clearly documented.


387-429: LGTM!

Image update flow correctly threads the preserveCompose flag through to pullAndPersist.


431-451: LGTM!

Confirmation helpers follow coding guidelines by using charmbracelet/huh for interactive forms, with proper non-interactive mode handling.


453-538: LGTM!

Robust implementation with proper rollback on failure and clear error handling. The air-gapped environment hint is helpful UX.


540-602: LGTM!

patchComposeImageRefs correctly validates that all required services were patched before succeeding, preventing config state advancement when compose is unpatched. Clear error messages guide users to regenerate via synthorg init.


604-665: LGTM!

Restart logic handles both interactive and non-interactive modes appropriately, with reasonable health check timeout and error handling.


242-260: No action needed — path is properly sanitized.

The composePath derivation is safe. safeStateDir() calls config.SecurePath(state.DataDir), which uses filepath.Clean() to normalize the path and filepath.IsAbs() to enforce absolute paths, rejecting relative paths and traversal attempts like ../etc/passwd. The subsequent filepath.Join(safeDir, "compose.yml") is secure.

Aureliolo and others added 2 commits March 19, 2026 12:38
Symmetric with the existing "trailing newline added" case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These Docker container log files were picked up by git add -A.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 11:40 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@cli/cmd/update_test.go`:
- Around line 124-174: Extend TestRedactSecret to add targeted edge-case
table-driven tests for the redactSecret function: include cases for empty values
and missing quotes, values with single quotes, keys appearing as substrings
(e.g., NOT_A_SECRET_KEY), lines with extra whitespace or tabs, inline comments
after the value, multiple colons or YAML-style mappings, and mixed casing and
punctuation; keep tests in the same TestRedactSecret harness and add distinct
names for each case so redactSecret behavior is validated across these edge
conditions.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9af53ce9-bad4-49b7-ac2a-4a614a61b4d0

📥 Commits

Reviewing files that changed from the base of the PR and between e487997 and 52e8c05.

📒 Files selected for processing (1)
  • cli/cmd/update_test.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
cli/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cli/**/*.go: Use Cobra framework for CLI commands in the Go CLI binary
Use charmbracelet/lipgloss for styled CLI output and charmbracelet/huh for interactive forms in the Go CLI
Run golangci-lint, go vet, and go test for linting and testing all Go CLI code

Files:

  • cli/cmd/update_test.go
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Dependabot: auto-updates Docker image digests and versions daily.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:29:37.522Z
Learning: Applies to cli/**/*.{sh,ps1} : Install scripts (install.sh, install.ps1) should provide cross-platform CLI installation with automated self-update capability
📚 Learning: 2026-03-19T11:29:37.522Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:29:37.522Z
Learning: Applies to cli/**/*.go : Run golangci-lint, go vet, and go test for linting and testing all Go CLI code

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to cli/** : CLI: Go 1.26+, dependencies in cli/go.mod (Cobra, charmbracelet/huh).

Applied to files:

  • cli/cmd/update_test.go
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Pre-push hooks: mypy type-check + pytest unit tests + golangci-lint + go vet + go test (CLI, conditional on cli/**/*.go) (fast gate before push, skipped in pre-commit.ci — dedicated CI jobs already run these).

Applied to files:

  • cli/cmd/update_test.go
🔇 Additional comments (10)
cli/cmd/update_test.go (10)

3-13: LGTM!

Clean import organization with only standard library and internal project packages.


39-111: LGTM!

Well-structured table-driven tests with good coverage including edge cases for trailing newlines. The addition of the "trailing newline removed" case provides symmetrical coverage.


113-122: LGTM!

Good use of fuzzing to ensure lineDiff doesn't panic on arbitrary input combinations.


176-190: LGTM!

Thorough verification of sentinel error identity semantics including the wrapped case with %w.


192-202: LGTM!

Clean test verifying graceful handling when compose.yml doesn't exist.


204-234: LGTM!

The permission test properly handles all edge cases: Windows skip, root/container environment detection with pre-test probe, and defensive permission restoration before assertions. Past review feedback has been well incorporated.


236-292: LGTM!

Comprehensive test validating that patchComposeImageRefs updates image references while preserving non-image configuration and the original CLI version comment. This correctly tests the compose-preservation behavior for users who decline regeneration.


294-323: LGTM!

Good coverage of the tag-based fallback path when digest pins are not provided.


325-343: LGTM!

Good error case coverage ensuring the function fails gracefully for compose files without SynthOrg image references.


345-364: LGTM!

Good validation test ensuring that required services are present before patching proceeds.

Comment on lines +124 to +174
func TestRedactSecret(t *testing.T) {
tests := []struct {
name string
line string
want string
}{
{
name: "jwt secret redacted",
line: ` SYNTHORG_JWT_SECRET: "supersecret123"`,
want: ` SYNTHORG_JWT_SECRET: [REDACTED]`,
},
{
name: "non-secret line unchanged",
line: ` SYNTHORG_LOG_DIR: "/data/logs"`,
want: ` SYNTHORG_LOG_DIR: "/data/logs"`,
},
{
name: "case insensitive match",
line: ` synthorg_jwt_secret: "abc"`,
want: ` synthorg_jwt_secret: [REDACTED]`,
},
{
name: "token key redacted",
line: ` AUTH_TOKEN: "mytoken"`,
want: ` AUTH_TOKEN: [REDACTED]`,
},
{
name: "password key redacted",
line: ` DB_PASSWORD: "hunter2"`,
want: ` DB_PASSWORD: [REDACTED]`,
},
{
name: "api key redacted",
line: ` EXTERNAL_API_KEY: "key123"`,
want: ` EXTERNAL_API_KEY: [REDACTED]`,
},
{
name: "credentials key redacted",
line: ` SERVICE_CREDENTIALS: "creds"`,
want: ` SERVICE_CREDENTIALS: [REDACTED]`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := redactSecret(tt.line)
if got != tt.want {
t.Errorf("redactSecret(%q) = %q, want %q", tt.line, got, tt.want)
}
})
}
}
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 | 🔵 Trivial

Good coverage of secret redaction patterns.

The tests cover the main secret key patterns (JWT_SECRET, TOKEN, PASSWORD, API_KEY, CREDENTIALS) and verify case-insensitivity.

Consider adding edge case tests for robustness:

♻️ Optional: Additional edge cases
 		{
 			name: "credentials key redacted",
 			line: `      SERVICE_CREDENTIALS: "creds"`,
 			want: `      SERVICE_CREDENTIALS: [REDACTED]`,
 		},
+		{
+			name: "empty line unchanged",
+			line: "",
+			want: "",
+		},
+		{
+			name: "line without colon unchanged",
+			line: "# comment mentioning SECRET",
+			want: "# comment mentioning SECRET",
+		},
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/cmd/update_test.go` around lines 124 - 174, Extend TestRedactSecret to
add targeted edge-case table-driven tests for the redactSecret function: include
cases for empty values and missing quotes, values with single quotes, keys
appearing as substrings (e.g., NOT_A_SECRET_KEY), lines with extra whitespace or
tabs, inline comments after the value, multiple colons or YAML-style mappings,
and mixed casing and punctuation; keep tests in the same TestRedactSecret
harness and add distinct names for each case so redactSecret behavior is
validated across these edge conditions.

Add 9 edge-case tests: empty value, single quotes, keyword substring,
tab indent, inline comments, multiple colons, mixed case, no indent,
and non-secret with colon in value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 11:51 — with GitHub Actions Inactive
@Aureliolo Aureliolo merged commit 3f226eb into main Mar 19, 2026
41 checks passed
@Aureliolo Aureliolo deleted the fix/cli-update-compose-refresh branch March 19, 2026 11:57
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 19, 2026 11:57 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.7](v0.3.6...v0.3.7)
(2026-03-19)


### Features

* **engine:** implement Hybrid Plan + ReAct execution loop
([#582](#582))
([008147c](008147c))
* implement first-run setup wizard
([#584](#584))
([dfed931](dfed931))


### Bug Fixes

* **api:** address ZAP DAST scan findings
([#579](#579))
([ce9a3e0](ce9a3e0))
* **cli:** regenerate compose and re-exec binary on update
([#576](#576))
([3f226eb](3f226eb))


### CI/CD

* add SBOM generation to Docker and CLI releases
([#580](#580))
([db459cf](db459cf))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Aureliolo added a commit that referenced this pull request Mar 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.7](v0.3.6...v0.3.7)
(2026-03-19)


### Features

* **engine:** implement Hybrid Plan + ReAct execution loop
([#582](#582))
([008147c](008147c))
* implement first-run setup wizard
([#584](#584))
([dfed931](dfed931))


### Bug Fixes

* **api:** address ZAP DAST scan findings
([#579](#579))
([ce9a3e0](ce9a3e0))
* **cli:** auto-delete binary on Windows, prune images, fix GoReleaser
([#590](#590))
([eb7c691](eb7c691))
* **cli:** regenerate compose and re-exec binary on update
([#576](#576))
([3f226eb](3f226eb))


### CI/CD

* add SBOM generation to Docker and CLI releases
([#580](#580))
([db459cf](db459cf))


### Maintenance

* **main:** release 0.3.7
([#583](#583))
([bf58779](bf58779))
* reset failed v0.3.7 release
([#591](#591))
([b69000d](b69000d))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added engine Hybrid Plan + ReAct execution loop
  * Added first-run setup wizard

* **Bug Fixes**
  * Addressed ZAP DAST scan issues
  * Fixed CLI Windows/image/update issues

* **Maintenance**
  * Added SBOM generation for Docker/CLI releases
  * General maintenance updates

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Aureliolo added a commit that referenced this pull request Mar 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.7](v0.3.6...v0.3.7)
(2026-03-19)


### Features

* **engine:** implement Hybrid Plan + ReAct execution loop
([#582](#582))
([008147c](008147c))
* implement first-run setup wizard
([#584](#584))
([dfed931](dfed931))


### Bug Fixes

* **api:** address ZAP DAST scan findings
([#579](#579))
([ce9a3e0](ce9a3e0))
* **ci:** reset failed v0.3.7 release and fix syft SBOM scan
([#593](#593))
([d1508c2](d1508c2))
* **cli:** auto-delete binary on Windows, prune images, fix GoReleaser
([#590](#590))
([eb7c691](eb7c691))
* **cli:** regenerate compose and re-exec binary on update
([#576](#576))
([3f226eb](3f226eb))


### CI/CD

* add SBOM generation to Docker and CLI releases
([#580](#580))
([db459cf](db459cf))


---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
Aureliolo added a commit that referenced this pull request Mar 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.3.7](v0.3.6...v0.3.7)
(2026-03-19)


### Features

* **engine:** implement Hybrid Plan + ReAct execution loop
([#582](#582))
([008147c](008147c))
* implement first-run setup wizard
([#584](#584))
([dfed931](dfed931))


### Bug Fixes

* **api:** address ZAP DAST scan findings
([#579](#579))
([ce9a3e0](ce9a3e0))
* **ci:** remove CLI SBOM generation, reset failed v0.3.7
([#595](#595))
([d0f4992](d0f4992))
* **ci:** reset failed v0.3.7 release and fix syft SBOM scan
([#593](#593))
([d1508c2](d1508c2))
* **cli:** auto-delete binary on Windows, prune images, fix GoReleaser
([#590](#590))
([eb7c691](eb7c691))
* **cli:** regenerate compose and re-exec binary on update
([#576](#576))
([3f226eb](3f226eb))


### CI/CD

* add SBOM generation to Docker and CLI releases
([#580](#580))
([db459cf](db459cf))




---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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