Skip to content

[sync] go-devops: 10 commits from Forge#1

Merged
Snider merged 10 commits intomainfrom
dev
Mar 17, 2026
Merged

[sync] go-devops: 10 commits from Forge#1
Snider merged 10 commits intomainfrom
dev

Conversation

@Snider
Copy link
Contributor

@Snider Snider commented Mar 17, 2026

Forge → GitHub Sync

Commits: 10
Files changed: 27

Automated sync from Forge (forge.lthn.ai) to GitHub mirror.


Co-Authored-By: Virgil [email protected]

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new tag subcommand under dev for managing repository versioning with support for dry-run and force modes, enabling automated tagging with dependency-ordered execution.
    • Introduced English localisation support across the CLI.
  • Bug Fixes & Improvements

    • Enhanced error handling with more structured error reporting throughout the application.
  • Dependencies

    • Updated core packages and transitive dependencies to latest compatible versions.

Snider and others added 10 commits March 15, 2026 15:38
Reads repos.yaml dependency graph via TopologicalOrder(), bumps patch
version bottom-up, runs GOWORK=off go get -u ./... and go mod tidy per
repo, commits go.mod/go.sum, creates annotated tags, and pushes.

Supports --dry-run to preview the plan and --force to skip confirmation.

Co-Authored-By: Virgil <[email protected]>
…h framework equivalents

Replace os.ReadFile with coreio.Local.Read for consistent filesystem abstraction.
Replace fmt.Errorf/errors.New with log.E() from go-log for structured error context.

Co-Authored-By: Virgil <[email protected]>
281 translation keys covering dev (health, work, commit, push, pull,
tag, impact, issues, reviews, ci, apply, workflow, vm), deploy, docs,
git, and setup commands.

Co-Authored-By: Virgil <[email protected]>
Locales auto-load when cmd/dev is imported via init().

Co-Authored-By: Virgil <[email protected]>
Clean init(): cli.RegisterCommands(AddDevCommands, locales.FS)
No more i18n.AddLoader — CLI handles locale loading automatically.

Co-Authored-By: Virgil <[email protected]>
Package-level var declarations run at import time, before i18n is
initialised. Move Short/Long assignment to AddCommands functions
which run after Core startup.

Co-Authored-By: Virgil <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Warning

Rate limit exceeded

@Snider has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 11 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 278eb9fc-b542-4a50-9d73-aeef720333ad

📥 Commits

Reviewing files that changed from the base of the PR and between 0f50f98 and 3bdc9a7.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml
📝 Walkthrough

Walkthrough

The changes integrate internationalization support throughout the CLI by adding locale files and refactoring command initialization. Error handling is standardised to use a logging wrapper (log.E) instead of direct error constructors. A new tag subcommand is introduced under the dev command to manage repository versioning and git operations. Dependencies are updated to newer versions.

Changes

Cohort / File(s) Summary
Internationalization Integration
cmd/deploy/cmd_commands.go, cmd/dev/cmd_dev.go, cmd/docs/cmd_commands.go, cmd/docs/cmd_docs.go, cmd/setup/cmd_commands.go, cmd/setup/cmd_setup.go, locales/embed.go, locales/en.json
Added locales import and passing locales.FS to RegisterCommands. Introduced setDeployI18n() and setDocsI18n() helper functions to populate command translations. Removed hardcoded i18n.T calls from struct literals and consolidated i18n initialisation into dedicated functions. Created locales/embed.go exporting embedded FS and locales/en.json with comprehensive translation keys covering commands, flags, status messages, and error strings.
Error Handling Refactoring
cmd/deploy/cmd_deploy.go, cmd/dev/cmd_impact.go, cmd/dev/cmd_vm.go, cmd/dev/forge_client.go, cmd/setup/cmd_bootstrap.go, cmd/setup/cmd_github.go, cmd/setup/cmd_registry.go, cmd/setup/cmd_repo.go, cmd/setup/github_config.go, cmd/setup/github_protection.go, cmd/setup/github_security.go, cmd/setup/github_webhooks.go, deploy/coolify/client.go, snapshot/snapshot.go
Systematically replaced fmt.Errorf and errors.New calls with structured logging via log.E across error paths. Added log package imports and removed unused errors/fmt imports. Error messages preserved but now routed through logging wrapper with category context (e.g., "setup.github", "dev.vm").
New Tag Subcommand
cmd/dev/cmd_tag.go
Added new AddTagCommand function registering tag subcommand under dev with flags: --registry, --dry-run, --force. Implemented dependency-ordered tagging workflow including go module updates, version bumping, git tag creation, and push operations. Provides helper functions for version management, git operations, and file checking.
Documentation Metadata Cleanup
cmd/docs/cmd_list.go, cmd/docs/cmd_sync.go
Removed Short and Long localisation fields from command struct literals, retaining only Use field. Translations now assigned via setDocsI18n() in cmd_docs.go.
Setup Command Metadata Refactoring
cmd/setup/cmd_setup.go
Removed i18n.T wrapped Short and Long fields from setupCmd literal, with translations now centralised in cmd_commands.go initialisation.
Dependency Updates
go.mod
Bumped multiple Forge LTHN core modules to v0.3.x/v0.1.x versions. Upgraded golang.org/x/term and golang.org/x/text to v0.41.0 and v0.35.0 respectively. Updated indirect dependencies including config, go-config, go-crypt, and Charmbracelet components. Minor removals and additions of various transitive dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI Tag Command
    participant Registry as Tag Registry
    participant GitRepo as Git Repository
    participant GoModules as Go Modules

    User->>CLI: Execute tag command (--registry, --dry-run, --force)
    CLI->>Registry: Load tag registry
    Registry-->>CLI: Registry with dependency graph
    CLI->>CLI: Compute dependency-ordered plan
    CLI-->>User: Display tagging plan per repo
    
    alt Dry-run mode
        CLI-->>User: Exit (plan only)
    else Normal mode
        alt Not forced
            CLI->>User: Prompt for confirmation
            User-->>CLI: Confirm
        end
        
        loop For each repository in dependency order
            CLI->>GitRepo: Check for go.mod
            alt go.mod exists
                CLI->>GoModules: Run go get -u ./...
                GoModules-->>CLI: Dependencies updated
                CLI->>GoModules: Run go mod tidy
                GoModules-->>CLI: Modules tidied
                CLI->>GitRepo: Commit go.mod/go.sum
                GitRepo-->>CLI: Committed
            end
            
            CLI->>GitRepo: Create annotated git tag (next version)
            GitRepo-->>CLI: Tag created
            CLI->>GitRepo: Push commits and tags (--follow-tags)
            GitRepo-->>CLI: Pushed to remote
        end
        
        CLI-->>User: Tagging complete
    end
Loading
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.91% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[sync] go-devops: 10 commits from Forge' clearly and concisely summarises the main change—an automated sync pull request from Forge containing 10 commits.

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

📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
deploy/coolify/client.go (1)

84-93: ⚠️ Potential issue | 🟠 Major

Avoid logging raw API output in error messages.

Line 92 embeds the full API response in the error message. This could expose sensitive data (credentials, PII, server configurations) in logs if the Coolify API returns such information in a malformed response.

Consider truncating the output or logging it at a debug level separately rather than embedding it in the error.

🔒 Proposed fix to limit output exposure
-		return nil, log.E("coolify", fmt.Sprintf("failed to parse response (output: %s)", output), err)
+		// Truncate output to avoid leaking sensitive data in logs
+		preview := output
+		if len(preview) > 100 {
+			preview = preview[:100] + "..."
+		}
+		return nil, log.E("coolify", fmt.Sprintf("failed to parse response (output: %s)", preview), err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@deploy/coolify/client.go` around lines 84 - 93, The error return in the JSON
parsing block currently embeds the full API response via the output variable in
log.E("coolify", ...), which can leak sensitive data; change the failure path in
the json.Unmarshal error branch (the block that also tries arrResult) to avoid
including raw output: construct a truncated or redacted representation (e.g.,
first N characters + "... [truncated]" or sanitize sensitive patterns) and use
that in the error message, and if you still want full output for debugging, log
the full output at debug level (not error) using the existing logger; update the
log.E call to reference the truncated/redacted string instead of output.
cmd/setup/cmd_registry.go (1)

215-215: ⚠️ Potential issue | 🟡 Minor

Incorrect i18n.T template argument format.

The call i18n.T("i18n.count.failed", failed) passes an integer directly, but the template expects a map with a Count key: "{{.Count}} failed". This will likely result in incorrect string interpolation.

🐛 Proposed fix
-		fmt.Printf(", %s", errorStyle.Render(i18n.T("i18n.count.failed", failed)))
+		fmt.Printf(", %s", errorStyle.Render(i18n.T("i18n.count.failed", map[string]any{"Count": failed})))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/setup/cmd_registry.go` at line 215, The i18n call currently passes the
integer directly to i18n.T which expects a map with a Count key; update the call
in cmd_registry.go where i18n.T("i18n.count.failed", failed) is used (inside the
fmt.Printf/errorStyle.Render expression) to pass a map with the Count field
(e.g., map[string]interface{}{"Count": failed}) so the template "{{.Count}}
failed" can interpolate correctly.
cmd/setup/cmd_setup.go (1)

35-40: ⚠️ Potential issue | 🟠 Major

Missing i18n initialisation for setup command.

The Short and Long fields were removed from setupCmd, but unlike other commands (e.g., setDocsI18n in cmd/docs/cmd_docs.go, setDeployI18n in cmd/deploy/cmd_deploy.go), there is no corresponding setSetupI18n() function defined or invoked. This will leave the setup command with empty descriptions in help output.

Proposed fix

Add a setSetupI18n function and call it in AddSetupCommand:

+func setSetupI18n() {
+	setupCmd.Short = i18n.T("cmd.setup.short")
+	setupCmd.Long = i18n.T("cmd.setup.long")
+}
+
 // AddSetupCommand adds the 'setup' command to the given parent command.
 func AddSetupCommand(root *cli.Command) {
+	setSetupI18n()
 	initSetupFlags()
 	addGitHubCommand(setupCmd)
 	root.AddCommand(setupCmd)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/setup/cmd_setup.go` around lines 35 - 40, The setup command lost its
localized descriptions; add a setSetupI18n function that sets setupCmd.Short and
setupCmd.Long (mirroring setDocsI18n/setDeployI18n patterns) and call
setSetupI18n() inside AddSetupCommand before the command is registered; update
the setSetupI18n implementation to use the same i18n/string resources and keys
as other commands so help text appears correctly for setupCmd.
🧹 Nitpick comments (1)
cmd/dev/cmd_tag.go (1)

101-129: A few runtime messages still bypass i18n.

The command metadata is localised, but the plan heading, dry-run notice, confirm prompt, and step labels in this block are still hard-coded English. Routing them through i18n.T(...) would keep dev tag aligned with the rest of the locale work in this PR.

Also applies to: 145-179

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dev/cmd_tag.go` around lines 101 - 129, Wrap all user-visible literal
strings in this block (and the later block around lines 145-179) through the
i18n system: replace the title passed to cli.TitleStyle.Render ("Tag plan
(dependency order)"), the arrow/step labels and the dry-run message ("Dry run —
no changes made."), and the confirmation prompt ("Tag and push %d repos?") with
i18n.T(...) calls; for formatted strings use fmt.Sprintf(i18n.T("..."),
len(plans)) or equivalent. Update usages in the rendering loop
(cli.Print/cli.Sprintf, repoNameStyle.Render, dimStyle.Render,
aheadStyle.Render), the dryRun branch (cli.Text), and the confirmation branch
(cli.Confirm) so all displayed text is produced via i18n.T.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/dev/cmd_tag.go`:
- Around line 144-177: The loop currently swallows per-repo failures and still
returns nil at the end; change the control flow so any error from goGetUpdate,
goModTidy, commitGoMod, createTag or pushWithTags causes the command to return a
non-nil error immediately instead of continuing the batch. Specifically, after
any failing call to goGetUpdate, goModTidy or commitGoMod, stop processing and
return the error; if the failure occurs after createTag (i.e., createTag
succeeded but a later step fails), delete the locally created tag for p.Next in
that repository before returning the error to avoid leaving a partial state.
Ensure the return uses the original error from the failing function so
callers/CI see failure.
- Around line 142-163: Before performing any modifications (the block guarded by
p.IsGoMod that calls goGetUpdate, goModTidy, and commitGoMod), add a preflight
"clean tree" check that runs "git status --porcelain" in p.Path and aborts with
an error if the output is non-empty; do the same for the other code path that
also calls commitGoMod (the section mentioned around lines 245–281).
Specifically, detect the repository state for the repo represented by p.Path,
and if any staged or uncommitted changes exist, print a clear error and
skip/bail instead of proceeding to goGetUpdate/goModTidy/commitGoMod so
commitGoMod never picks up unrelated staged changes. Ensure messages reference
the package (p.Path or p.Next) so the user knows which repo failed the
preflight.
- Around line 74-77: The code defaults to "v0.0.0" whenever latestTag(ctx,
repo.Path) returns an error or empty, but latestTag uses local refs only and can
return errors for stale/fresh clones; first fetch remote tags and preflight for
any tags before falling back. Update the call site (where current is set from
latestTag) to run a git fetch --tags (or equivalent fetchTags(ctx, repo.Path))
before calling latestTag, and modify latestTag to differentiate "no tags found"
(use git tag --list or git describe --always) from other git errors so you only
set current="v0.0.0" when there truly are no tags; reference the latestTag
function and the variable current/Next calculation to locate and fix the logic.
- Around line 247-265: The git add currently always includes both "go.mod" and
"go.sum" which fails when go.sum doesn't exist; change the add step (where
addCmd is constructed) to build the args list dynamically and only include
"go.mod" and "go.sum" when they actually exist or are tracked (use the existing
checks modChanged and untrackedSum or os.Stat/git ls-files) and then run
exec.CommandContext(ctx, "git", "add", <filtered args>) so addCmd only tries to
stage files that are present/tracked in repoPath.

In `@cmd/setup/cmd_registry.go`:
- Line 231: The i18n.T call is passing a raw string but the template expects a
map with a Name key; update the call at the log.E invocation so
i18n.T("i18n.fail.run", ...) receives a map with Name set to "build" (i.e.,
replace the second argument with a map[string]string or equivalent
{"Name":"build"}) so the template {{.Name}} can be populated; the change should
be made where log.E("setup.registry", i18n.T(...), err) is returned in
cmd_registry.go.

In `@locales/en.json`:
- Line 415: The localization key "conflicting_flags" currently reads "Cannot use
--check with modification flags" but the actual validation in cmd_github.go
compares ghRepo != "" && ghAll (conflict between --repo and --all); update the
value for the "conflicting_flags" key in locales/en.json to accurately describe
that conflict (e.g., "Cannot use --repo with --all" or similar) so the message
matches the check performed in cmd_github.go.

---

Outside diff comments:
In `@cmd/setup/cmd_registry.go`:
- Line 215: The i18n call currently passes the integer directly to i18n.T which
expects a map with a Count key; update the call in cmd_registry.go where
i18n.T("i18n.count.failed", failed) is used (inside the
fmt.Printf/errorStyle.Render expression) to pass a map with the Count field
(e.g., map[string]interface{}{"Count": failed}) so the template "{{.Count}}
failed" can interpolate correctly.

In `@cmd/setup/cmd_setup.go`:
- Around line 35-40: The setup command lost its localized descriptions; add a
setSetupI18n function that sets setupCmd.Short and setupCmd.Long (mirroring
setDocsI18n/setDeployI18n patterns) and call setSetupI18n() inside
AddSetupCommand before the command is registered; update the setSetupI18n
implementation to use the same i18n/string resources and keys as other commands
so help text appears correctly for setupCmd.

In `@deploy/coolify/client.go`:
- Around line 84-93: The error return in the JSON parsing block currently embeds
the full API response via the output variable in log.E("coolify", ...), which
can leak sensitive data; change the failure path in the json.Unmarshal error
branch (the block that also tries arrResult) to avoid including raw output:
construct a truncated or redacted representation (e.g., first N characters +
"... [truncated]" or sanitize sensitive patterns) and use that in the error
message, and if you still want full output for debugging, log the full output at
debug level (not error) using the existing logger; update the log.E call to
reference the truncated/redacted string instead of output.

---

Nitpick comments:
In `@cmd/dev/cmd_tag.go`:
- Around line 101-129: Wrap all user-visible literal strings in this block (and
the later block around lines 145-179) through the i18n system: replace the title
passed to cli.TitleStyle.Render ("Tag plan (dependency order)"), the arrow/step
labels and the dry-run message ("Dry run — no changes made."), and the
confirmation prompt ("Tag and push %d repos?") with i18n.T(...) calls; for
formatted strings use fmt.Sprintf(i18n.T("..."), len(plans)) or equivalent.
Update usages in the rendering loop (cli.Print/cli.Sprintf,
repoNameStyle.Render, dimStyle.Render, aheadStyle.Render), the dryRun branch
(cli.Text), and the confirmation branch (cli.Confirm) so all displayed text is
produced via i18n.T.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9a670936-3a60-4383-933e-b24cf1201066

📥 Commits

Reviewing files that changed from the base of the PR and between a2b74a6 and 0f50f98.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (26)
  • cmd/deploy/cmd_commands.go
  • cmd/deploy/cmd_deploy.go
  • cmd/dev/cmd_dev.go
  • cmd/dev/cmd_impact.go
  • cmd/dev/cmd_tag.go
  • cmd/dev/cmd_vm.go
  • cmd/dev/forge_client.go
  • cmd/docs/cmd_commands.go
  • cmd/docs/cmd_docs.go
  • cmd/docs/cmd_list.go
  • cmd/docs/cmd_sync.go
  • cmd/setup/cmd_bootstrap.go
  • cmd/setup/cmd_commands.go
  • cmd/setup/cmd_github.go
  • cmd/setup/cmd_registry.go
  • cmd/setup/cmd_repo.go
  • cmd/setup/cmd_setup.go
  • cmd/setup/github_config.go
  • cmd/setup/github_protection.go
  • cmd/setup/github_security.go
  • cmd/setup/github_webhooks.go
  • deploy/coolify/client.go
  • go.mod
  • locales/embed.go
  • locales/en.json
  • snapshot/snapshot.go

Comment on lines +74 to +77
current, err := latestTag(ctx, repo.Path)
if err != nil || current == "" {
current = "v0.0.0"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

According to the official Git documentation, how does git describe --tags --abbrev=0 --match "v*" behave, and how can tooling distinguish "no matching tags" from other command failures?

💡 Result:

Behavior of git describe --tags --abbrev=0 --match "v*" (per official docs)

  • git describe “finds the most recent tag that is reachable from a commit” (default: HEAD). If the commit is exactly tagged, it prints just the tag; otherwise it normally appends -<n>-g<abbrev> suffixes. With --abbrev=0, it “suppresses long format, only showing the closest tag.” [1]
  • --tags makes it consider any tag in refs/tags/ (including lightweight tags). [1]
  • --match "v*" restricts candidates to tags whose names match the given glob pattern (matched without the refs/tags/ prefix). [1]
  • If multiple tags are possible, the search strategy prefers: exact-match first; otherwise it walks history to find tagged ancestors; it chooses the tag with the fewest commits between it and the target commit, and prefers annotated tags over lightweight tags (and newer tag dates over older) when applicable. [1]

Distinguishing “no matching tags” vs other failures

  • Git’s fatal-error path (die()) exits with status 128. [2] So, exit status alone cannot reliably distinguish “no matching tags” from other fatal failures (both are typically 128).

Tooling patterns that do distinguish it:

  1. Avoid the fatal case by using a defined fallback
    Add --always, which “show[s a] uniquely abbreviated commit object as fallback.” [1]
    Then tooling can interpret:

    • output matching your tag naming scheme (e.g., starts with v) ⇒ found a matching tag
    • otherwise ⇒ “no matching tags” (but still a successful command invocation)
  2. Preflight-check whether any matching tags exist (and are reachable), then decide whether to call describe
    Use git tag --list with a pattern (shell wildcard) to test for candidates, and optionally --merged <commit> to restrict to tags reachable from HEAD (or another commit). [3]
    If that precheck returns no tags, treat it as “no matching tags” without ever invoking git describe; otherwise run git describe ... and treat nonzero exit as a real failure.

Sources:
[1] git-describe documentation (options, examples, and search strategy) https://git-scm.com/docs/git-describe/2.33.0.html
[2] api-error-handling (fatal die() exits 128) https://git-scm.com/docs/api-error-handling/2.32.0
[3] git-tag documentation (--list with patterns; --merged) https://git-scm.com/docs/git-tag


🏁 Script executed:

# First, check if the file exists and get its overall structure
cd cmd/dev && wc -l cmd_tag.go

Repository: dAppCore/go-devops

Length of output: 76


🏁 Script executed:

# Read the section around lines 74-77
sed -n '70,80p' cmd/dev/cmd_tag.go

Repository: dAppCore/go-devops

Length of output: 279


🏁 Script executed:

# Read the latestTag function implementation
sed -n '1,300p' cmd/dev/cmd_tag.go | grep -n "latestTag" -A 20

Repository: dAppCore/go-devops

Length of output: 1473


Fetch tags before determining version bumps to avoid stale or incorrect tag state.

latestTag (line 196) uses git describe on local refs only, without fetching. Lines 75–76 collapse all errors and empty results into v0.0.0, meaning a stale clone, fresh clone, or locally-created-but-unpushed tags will yield wrong Current/Next pairs. Per Git documentation, exit codes cannot reliably distinguish "no matching tags" from other failures—both exit 128. Fetch tags before planning, then only fall back when Git explicitly finds no matching tags (using git tag --list to preflight-check, or git describe --always to avoid fatal failures).

Also applies to: lines 195–203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dev/cmd_tag.go` around lines 74 - 77, The code defaults to "v0.0.0"
whenever latestTag(ctx, repo.Path) returns an error or empty, but latestTag uses
local refs only and can return errors for stale/fresh clones; first fetch remote
tags and preflight for any tags before falling back. Update the call site (where
current is set from latestTag) to run a git fetch --tags (or equivalent
fetchTags(ctx, repo.Path)) before calling latestTag, and modify latestTag to
differentiate "no tags found" (use git tag --list or git describe --always) from
other git errors so you only set current="v0.0.0" when there truly are no tags;
reference the latestTag function and the variable current/Next calculation to
locate and fix the logic.

Comment on lines +142 to +163
if p.IsGoMod {
// Step 1: GOWORK=off go get -u ./...
if err := goGetUpdate(ctx, p.Path); err != nil {
cli.Print(" %s go get -u: %s\n", errorStyle.Render("x"), err)
failed++
continue
}

// Step 2: GOWORK=off go mod tidy
if err := goModTidy(ctx, p.Path); err != nil {
cli.Print(" %s go mod tidy: %s\n", errorStyle.Render("x"), err)
failed++
continue
}

// Step 3: Commit go.mod/go.sum if changed
if err := commitGoMod(ctx, p.Path, p.Next); err != nil {
cli.Print(" %s commit: %s\n", errorStyle.Render("x"), err)
failed++
continue
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

According to the official Git documentation, does git commit -m ... include every currently staged file, even if only some paths were newly added in the same script?

💡 Result:

Yes. The official git commit documentation says that, when run without any pathnames, git commit “create[s] a new commit containing the current contents of the index” (the staging area). That means git commit -m "..." records everything that is currently staged, regardless of whether some paths were newly added earlier in the same script. [1]

By contrast, if you run git commit with path arguments, it records only those paths’ current working-tree contents and can ignore other staged changes. [1]

[1] Git official docs: git-commit (git-scm.com)


🏁 Script executed:

# First, let's find and examine the cmd/dev/cmd_tag.go file
find . -name "cmd_tag.go" -type f

Repository: dAppCore/go-devops

Length of output: 82


🏁 Script executed:

# Let's examine the full file to understand context
wc -l ./cmd/dev/cmd_tag.go

Repository: dAppCore/go-devops

Length of output: 86


🏁 Script executed:

# Read the entire file
cat -n ./cmd/dev/cmd_tag.go

Repository: dAppCore/go-devops

Length of output: 10604


Add a clean-tree preflight check before modifying repositories.

The commitGoMod function at line 277 runs git commit without path arguments, which commits everything currently staged. If the working tree has pre-existing staged files before the script starts, they will be included in the auto-generated commit, making those changes indistinguishable from the go.mod/go.sum updates. Add a check before line 142 to verify the repository is clean—i.e., git status --porcelain is empty—and bail out if not.

Also applies to: lines 245–281

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dev/cmd_tag.go` around lines 142 - 163, Before performing any
modifications (the block guarded by p.IsGoMod that calls goGetUpdate, goModTidy,
and commitGoMod), add a preflight "clean tree" check that runs "git status
--porcelain" in p.Path and aborts with an error if the output is non-empty; do
the same for the other code path that also calls commitGoMod (the section
mentioned around lines 245–281). Specifically, detect the repository state for
the repo represented by p.Path, and if any staged or uncommitted changes exist,
print a clear error and skip/bail instead of proceeding to
goGetUpdate/goModTidy/commitGoMod so commitGoMod never picks up unrelated staged
changes. Ensure messages reference the package (p.Path or p.Next) so the user
knows which repo failed the preflight.

Comment on lines +144 to +177
if err := goGetUpdate(ctx, p.Path); err != nil {
cli.Print(" %s go get -u: %s\n", errorStyle.Render("x"), err)
failed++
continue
}

// Step 2: GOWORK=off go mod tidy
if err := goModTidy(ctx, p.Path); err != nil {
cli.Print(" %s go mod tidy: %s\n", errorStyle.Render("x"), err)
failed++
continue
}

// Step 3: Commit go.mod/go.sum if changed
if err := commitGoMod(ctx, p.Path, p.Next); err != nil {
cli.Print(" %s commit: %s\n", errorStyle.Render("x"), err)
failed++
continue
}
}

// Step 4: Create annotated tag
if err := createTag(ctx, p.Path, p.Next); err != nil {
cli.Print(" %s tag: %s\n", errorStyle.Render("x"), err)
failed++
continue
}

// Step 5: Push commits and tags
if err := pushWithTags(ctx, p.Path); err != nil {
cli.Print(" %s push: %s\n", errorStyle.Render("x"), err)
failed++
continue
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not report success after a partial batch failure.

This loop carries on after every repo error, and Line 191 still returns nil. In a dependency-ordered batch that can leave downstream repos working from an incomplete state while CI/scripts see success, so the command should return a non-nil error once any repo fails; if the failure happens after Line 166, clean up the just-created local tag before exiting.

Also applies to: 183-191

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dev/cmd_tag.go` around lines 144 - 177, The loop currently swallows
per-repo failures and still returns nil at the end; change the control flow so
any error from goGetUpdate, goModTidy, commitGoMod, createTag or pushWithTags
causes the command to return a non-nil error immediately instead of continuing
the batch. Specifically, after any failing call to goGetUpdate, goModTidy or
commitGoMod, stop processing and return the error; if the failure occurs after
createTag (i.e., createTag succeeded but a later step fails), delete the locally
created tag for p.Next in that repository before returning the error to avoid
leaving a partial state. Ensure the return uses the original error from the
failing function so callers/CI see failure.

Comment on lines +247 to +265
diffCmd := exec.CommandContext(ctx, "git", "diff", "--quiet", "go.mod", "go.sum")
diffCmd.Dir = repoPath
modChanged := diffCmd.Run() != nil

// Also check for untracked go.sum
lsCmd := exec.CommandContext(ctx, "git", "ls-files", "--others", "--exclude-standard", "go.sum")
lsCmd.Dir = repoPath
lsOut, _ := lsCmd.Output()
untrackedSum := strings.TrimSpace(string(lsOut)) != ""

if !modChanged && !untrackedSum {
return nil // No changes
}

// Stage go.mod and go.sum
addCmd := exec.CommandContext(ctx, "git", "add", "go.mod", "go.sum")
addCmd.Dir = repoPath
if out, err := addCmd.CombinedOutput(); err != nil {
return log.E("dev.tag", "git add: "+strings.TrimSpace(string(out)), err)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

According to the official Git documentation, what happens when git add go.mod go.sumis run andgo.sum does not exist or is not tracked?

💡 Result:

  • git add go.mod go.sum “updates the index using the current content found in the working tree” for the given pathspecs, i.e., it stages those files for the next commit. (git-scm.com)
  • If go.sum exists in your working tree but is untracked, git add will add it (stage it) like any other “new … file[]”. (git-scm.com)
  • If go.sum does not exist (so the pathspec matches nothing), Git errors with: error: pathspec 'go.sum' did not match any file(s) known to git. (git-scm.com)
  • The --ignore-missing option does not apply here because, per the docs, it “can only be used together with --dry-run”. (git-scm.com)

Citations:


Stage only files that exist or are already tracked.

Line 262 unconditionally stages both go.mod and go.sum, but valid Go modules can legitimately have no go.sum. When go.sum doesn't exist, git add fails with error: pathspec 'go.sum' did not match any file(s) known to git, breaking the tag flow even when only go.mod changed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/dev/cmd_tag.go` around lines 247 - 265, The git add currently always
includes both "go.mod" and "go.sum" which fails when go.sum doesn't exist;
change the add step (where addCmd is constructed) to build the args list
dynamically and only include "go.mod" and "go.sum" when they actually exist or
are tracked (use the existing checks modChanged and untrackedSum or os.Stat/git
ls-files) and then run exec.CommandContext(ctx, "git", "add", <filtered args>)
so addCmd only tries to stage files that are present/tracked in repoPath.

buildCmd.Stderr = os.Stderr
if err := buildCmd.Run(); err != nil {
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.run", "build"), err)
return log.E("setup.registry", i18n.T("i18n.fail.run", "build"), err)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect i18n.T template argument format.

The call i18n.T("i18n.fail.run", "build") passes a string directly, but the template "Failed to run {{.Name}}" expects a map with a Name key.

🐛 Proposed fix
-			return log.E("setup.registry", i18n.T("i18n.fail.run", "build"), err)
+			return log.E("setup.registry", i18n.T("i18n.fail.run", map[string]any{"Name": "build"}), err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return log.E("setup.registry", i18n.T("i18n.fail.run", "build"), err)
return log.E("setup.registry", i18n.T("i18n.fail.run", map[string]any{"Name": "build"}), err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/setup/cmd_registry.go` at line 231, The i18n.T call is passing a raw
string but the template expects a map with a Name key; update the call at the
log.E invocation so i18n.T("i18n.fail.run", ...) receives a map with Name set to
"build" (i.e., replace the second argument with a map[string]string or
equivalent {"Name":"build"}) so the template {{.Name}} can be populated; the
change should be made where log.E("setup.registry", i18n.T(...), err) is
returned in cmd_registry.go.

"error": {
"not_authenticated": "GitHub CLI (gh) is not authenticated. Run 'gh auth login' first.",
"config_not_found": "GitHub config file not found",
"conflicting_flags": "Cannot use --check with modification flags"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Error message may not match the validation logic.

The message "conflicting_flags": "Cannot use --check with modification flags" doesn't match the actual validation in cmd_github.go (line 120-121), which checks for ghRepo != "" && ghAll (using --repo with --all together). Consider updating the message to accurately describe the conflict:

-        "conflicting_flags": "Cannot use --check with modification flags"
+        "conflicting_flags": "Cannot use --repo with --all"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"conflicting_flags": "Cannot use --check with modification flags"
"conflicting_flags": "Cannot use --repo with --all"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@locales/en.json` at line 415, The localization key "conflicting_flags"
currently reads "Cannot use --check with modification flags" but the actual
validation in cmd_github.go compares ghRepo != "" && ghAll (conflict between
--repo and --all); update the value for the "conflicting_flags" key in
locales/en.json to accurately describe that conflict (e.g., "Cannot use --repo
with --all" or similar) so the message matches the check performed in
cmd_github.go.

@Snider Snider merged commit 727ac3e into main Mar 17, 2026
1 check passed
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.

1 participant