Skip to content

Addressing PR comments#145

Open
Claude wants to merge 22 commits into
mainfrom
claude/improve-deploy-time-error-classification
Open

Addressing PR comments#145
Claude wants to merge 22 commits into
mainfrom
claude/improve-deploy-time-error-classification

Conversation

@Claude
Copy link
Copy Markdown
Contributor

@Claude Claude AI commented Mar 17, 2026


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@Claude Claude AI assigned Claude and ewega Mar 17, 2026
@Claude Claude AI linked an issue Mar 17, 2026 that may be closed by this pull request
8 tasks
Claude AI and others added 2 commits March 17, 2026 00:45
Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
@Claude Claude AI changed the title [WIP] Improve deploy-time error classification and bounded recovery Add deploy-time error classification and bounded port fallback recovery Mar 17, 2026
@Claude Claude AI requested a review from ewega March 17, 2026 00:48
@ewega ewega marked this pull request as ready for review March 17, 2026 01:27
Copilot AI review requested due to automatic review settings March 17, 2026 01:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves gh devlake deploy resilience by introducing deploy-time error classification (focused on Docker port conflicts) and adding bounded recovery paths for both local and Azure deployments.

Changes:

  • Added DeployError classification helpers for Docker Compose failures, including port extraction and conflicting container detection.
  • Implemented a bounded local deploy recovery: on detected port conflict, rewrite compose ports to an alternate bundle and retry once.
  • Normalized Azure deploy recovery messaging for login, stopped MySQL servers, and soft-deleted Key Vault conflicts; updated deployment docs accordingly.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/deploy.md Documents local port fallback behavior and Azure bounded recovery messaging.
cmd/deploy_local.go Adds bounded port-fallback retry logic and compose port rewriting for local deploys.
cmd/deploy_local_test.go Adds tests for compose port rewriting behavior and error cases.
cmd/deploy_errors.go Introduces Docker Compose error classification, port extraction, and port-owner discovery.
cmd/deploy_errors_test.go Adds unit tests for port extraction and Docker Compose error classification patterns.
cmd/deploy_azure.go Updates Azure recovery output to a consistent detection → recovery → outcome format.
Comments suppressed due to low confidence (3)

docs/deploy.md:74

  • The documented port-conflict patterns list omits bind for, but classifyDockerComposeError treats bind for as one of the recognized patterns. Please align the docs with the actual classifier so users know all the strings that trigger the recovery path.
- **Port conflicts**: When deploying with official or fork images, the CLI detects port conflicts (patterns: `port is already allocated`, `ports are not available`, `address already in use`, `failed programming external connectivity`) and automatically retries with alternate ports (`8085/3004/4004`). Recovery is bounded to a single retry.
- **Custom deployments**: Port conflicts in custom deployments require manual resolution — the CLI will identify the conflicting container and suggest remediation commands.

docs/deploy.md:76

  • The local section claims “State checkpointing” saves deployment state early for mid-flight cleanup, but deploy local doesn’t appear to write .devlake-local.json at all (cleanup falls back to docker-compose.yml when no state exists). Please clarify what state is checkpointed for local deploys, or update the implementation/docs so the claim matches actual behavior.
- **State checkpointing**: Deployment state is saved early to enable cleanup even when deployment fails mid-flight.

cmd/deploy_local.go:200

  • With port fallback, backendURL can now be http://localhost:8085, but the later user-facing summary still prints Config UI/Grafana URLs using the default ports (4000/3002). That will mislead users after a successful fallback because the compose file ports are rewritten to 4004/3004. Consider deriving the full endpoint bundle from the selected backend port (8080→{3002,4000}, 8085→{3004,4004}) and printing the matching URLs.
		// Allow alternate port bundle for official/fork (not custom)
		allowPortFallback := deployLocalSource != "custom"

		backendURL, err := startLocalContainers(absDir, buildImages, allowPortFallback, services...)
		if err != nil {
			return err
		}
		cfgURL = backendURL

Comment thread docs/deploy.md Outdated
| Config UI | http://localhost:4000 or http://localhost:4004 | — |
| Grafana | http://localhost:3002 or http://localhost:3004 | admin / admin |

**Port Fallback**: When deploying with `--source official` or `--source fork`, the CLI automatically recovers from port conflicts by retrying with alternate ports (`8085/3004/4004`). Custom deployments require manual port conflict resolution.
Comment thread cmd/deploy_errors.go Outdated
Comment on lines +179 to +197
// isValidPort checks if a string looks like a valid port number (all digits, 1-65535).
func isValidPort(s string) bool {
if len(s) < 1 || len(s) > 5 {
return false
}
for _, ch := range s {
if ch < '0' || ch > '9' {
return false
}
}
// Basic range check (ports are 1-65535)
if len(s) == 5 {
// Quick check: if > 65535, invalid
if s > "65535" {
return false
}
}
return true
}
Comment thread cmd/deploy_local.go Outdated
Comment on lines +468 to +479
fmt.Println()
fmt.Printf("🔧 Port conflict detected on default ports (8080/3002/4000)\n")
if deployErr.Port != "" {
fmt.Printf(" Port %s is in use", deployErr.Port)
if deployErr.Container != "" {
fmt.Printf(" by container: %s", deployErr.Container)
}
fmt.Println()
}
fmt.Println()
fmt.Println("🔄 Retrying with alternate ports (8085/3004/4004)...")

Comment thread cmd/deploy_local.go Outdated
Comment on lines +541 to +565
// Port mapping patterns:
// - "8080:8080" -> "8085:8080" (external:internal)
// - "3002:3002" -> "3004:3002"
// - "4000:4000" -> "4004:4000"
portMappings := map[string]string{
"8080:8080": "8085:8080",
"- 8080:8080": "- 8085:8080",
"\"8080:8080\"": "\"8085:8080\"",
"'8080:8080'": "'8085:8080'",

"3002:3002": "3004:3002",
"- 3002:3002": "- 3004:3002",
"\"3002:3002\"": "\"3004:3002\"",
"'3002:3002'": "'3004:3002'",

"4000:4000": "4004:4000",
"- 4000:4000": "- 4004:4000",
"\"4000:4000\"": "\"4004:4000\"",
"'4000:4000'": "'4004:4000'",
}

modified := content
for old, new := range portMappings {
modified = strings.ReplaceAll(modified, old, new)
}
Comment thread cmd/deploy_errors.go Outdated
Comment on lines +252 to +256
fmt.Println()
if de.Port != "" {
fmt.Printf("❌ Port conflict detected: port %s is already in use.\n", de.Port)
} else {
fmt.Println("❌ Port conflict detected: a required port is already in use.")
@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 17, 2026

@claude[agent] Please address the actionable findings from the fresh Copilot review on this PR:

  1. In cmd/deploy_errors.go, fix isValidPort so it only accepts ports in the inclusive range 1..65535; reject 0/00000 and avoid misleading docker ps --filter publish=0 diagnostics.
  2. In cmd/deploy_local.go, harden rewriteComposePorts so it rewrites only the exact intended host-port mappings; it must not mutate unrelated custom ports like 18080:8080 -> 18085:8080. Please add/adjust tests for this case.
  3. In cmd/deploy_local.go, after successful alternate-port fallback, print the matching endpoint bundle in the final success summary/manual guidance instead of always showing the default Config UI/Grafana ports.
  4. In docs/deploy.md, either document the local --source/related flags that are referenced later or reword that section so it does not point users at undocumented flags. Also align the documented port-conflict trigger patterns with the classifier (include bind for if that remains part of detection).
  5. Update the emoji-prefixed step lines called out in cmd/deploy_local.go and cmd/deploy_errors.go so they include the leading newline in the same print call per .github/instructions/terminal-output.instructions.md.

Please push a follow-up commit and keep checks green.

@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 17, 2026

@claude[agent] please address the actionable review feedback on this PR:

  1. In isValidPort, enforce the documented valid range 1..65535 so 0 / 00000 are rejected before conflict lookup runs.
  2. In rewriteComposePorts, avoid raw substring replacement that can mutate unrelated host ports like 18080:8080 -> 18085:8080. Rewrite only the exact intended host-port mappings (regex with boundaries or structured YAML rewrite is fine).
  3. When local deploy succeeds on the fallback bundle (8085/3004/4004), make the final user-facing endpoint summary print the matching Config UI and Grafana URLs instead of the default 4000/3002.
  4. Align docs/deploy.md with the implementation: if the local docs reference --source / related flags, make sure those flags are actually documented; include all recognized port-conflict patterns (including bind for if that is intentional); and remove or correct the local state checkpointing claim if local deploy does not actually persist that state.
  5. While touching the reviewed areas, please also make the emoji-prefixed step lines follow the terminal output convention of including the leading newline in the same print call at the cited locations.

After pushing the fixes, I’ll re-run the review/check pass.

…RLs, docs, and terminal output

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented Mar 17, 2026

@claude[agent] Please address the actionable findings from the fresh Copilot review on this PR:

  1. In cmd/deploy_errors.go, fix isValidPort so it only accepts ports in the inclusive range 1..65535; r...

All findings addressed in e358254:

  1. Port validation: isValidPort now properly validates 1-65535 range, rejects 0/00000/65536
  2. Port rewriting safety: Uses regex with boundaries (?m)(^\s*-\s*)["']?8080:8080["']? to avoid rewriting custom ports like 18080:8080
  3. Dynamic endpoint URLs: Added inferCompanionURLs() that detects backend port (8080 vs 8085) and shows matching Grafana/Config UI URLs in success summary
  4. Documentation: Added --source, --repo-url, --start flags to table; included "bind for" in error patterns list
  5. Terminal output: Fixed emoji-prefixed steps to include leading \n in same print call per terminal-output.instructions.md

All tests pass, checks green.

@Claude Claude AI changed the title Add deploy-time error classification and bounded port fallback recovery Addressing PR comments Mar 17, 2026
@ewega ewega requested a review from Copilot March 17, 2026 01:42
@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 17, 2026

@copilot review the latest updates on this PR, especially the fixes for port validation, exact compose-port rewriting, fallback endpoint reporting, and deploy docs alignment.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates gh devlake local and Azure deployment UX/documentation to reflect bounded recovery behaviors (port fallback locally; login/MySQL/Key Vault recovery on Azure) and adds test coverage for the new deployment error classification and compose port rewriting.

Changes:

  • Add local deploy port-fallback support (single retry) by rewriting compose ports and detecting which backend port is healthy.
  • Introduce Docker Compose error classification + port-owner detection helpers, with accompanying unit tests.
  • Update deployment docs to document new flags and resilience behaviors for local and Azure deployments.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
docs/deploy.md Documents new local flags, port fallback behavior, and bounded recovery behavior for local/Azure deploy flows.
cmd/deploy_local.go Implements bounded local port fallback, companion URL inference, and compose port rewriting.
cmd/deploy_local_test.go Adds tests for compose port rewriting behavior across multiple compose port syntaxes.
cmd/deploy_errors.go Adds Docker Compose error classification, port extraction, and port-owner lookup helpers.
cmd/deploy_errors_test.go Adds unit tests for port extraction + compose error classification patterns.
cmd/deploy_azure.go Adjusts Azure deploy output to clearly surface bounded recovery steps for login/MySQL/Key Vault.
Comments suppressed due to low confidence (1)

cmd/deploy_local.go:497

  • This sub-item print includes a leading blank line (\n ...), which introduces extra vertical spacing within the recovery block and breaks the “sub-items stay tight under their parent” rule. Consider removing the leading \n here and rely on the surrounding step banners for spacing.
	fmt.Println("\n   Starting containers with alternate ports...")
	err = dockerpkg.ComposeUp(absDir, build, services...)

Comment thread cmd/deploy_local.go Outdated
Comment on lines +502 to +503
fmt.Println()
fmt.Println("❌ Alternate ports are also in use.")
Comment thread cmd/deploy_local.go Outdated
Comment on lines +481 to +490
// Rewrite port mappings in docker-compose.yml or docker-compose-dev.yml
composePath := filepath.Join(absDir, "docker-compose.yml")
if _, err := os.Stat(composePath); os.IsNotExist(err) {
composePath = filepath.Join(absDir, "docker-compose-dev.yml")
}

if err := rewriteComposePorts(composePath); err != nil {
fmt.Printf(" ⚠️ Could not rewrite ports: %v\n", err)
printDockerPortConflictError(deployErr)
return "", fmt.Errorf("port conflict and failed to apply alternate ports: %w", err)
Comment thread cmd/deploy_local.go Outdated
modified := content

// Port mapping patterns with regex boundaries
// Match: "- 8080:8080" or "- "8080:8080"" or "- '8080:8080'" at start of list item
@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 17, 2026

@claude[agent] Fresh Copilot follow-up review landed with one substantive fix request plus one output-consistency follow-up. Please address these before the next review pass:

  1. In cmd/deploy_local.go, make the bounded port-fallback path detect when the compose file is already on the alternate bundle (or when the initial conflict is already on the alternate ports) and skip rewrite in that case. Right now it can try to rewrite an already-rewritten compose file, fail with no port mappings found, and miss the correct remediation path of simply reporting that the alternate ports are occupied.
  2. In the same recovery block, make the ❌ Alternate ports are also in use. step follow the terminal output rule by including the leading newline in the same print call (Copilot flagged this at lines 496/503).
  3. Optional cleanup while you are there: fix the extra quote in the nearby port-mapping comment example.

Please push the follow-up commit, then I’ll trigger another Copilot review/check pass.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

cmd/deploy_local.go:568

  • findComposeFile can return docker-compose-dev.yml, but startLocalContainers ultimately runs docker compose up via internal/docker.ComposeUp, which doesn’t pass -f and therefore won’t use docker-compose-dev.yml. This makes custom deployments that only provide docker-compose-dev.yml fail, despite being accepted earlier. Consider either (1) ensuring a docker-compose.yml exists (copy/rename dev → yml) before calling ComposeUp, or (2) extending the docker helper to accept an explicit compose file path and always using the result of findComposeFile.
// findComposeFile returns the path to the active docker compose file in the given directory.
// Checks for docker-compose.yml first, then docker-compose-dev.yml.
func findComposeFile(dir string) string {
	composePath := filepath.Join(dir, "docker-compose.yml")
	if _, err := os.Stat(composePath); err == nil {
		return composePath
	}
	return filepath.Join(dir, "docker-compose-dev.yml")
}

Comment thread cmd/deploy_local.go Outdated
fmt.Println("\n💡 To clean up partial artifacts:")
fmt.Println(" gh devlake cleanup --local --force")
return "", err
nextSteps := " Edit your docker-compose.yml to use different host ports, or stop the conflicting container."
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The remediation text hard-codes docker-compose.yml, but findComposeFile (and the earlier custom-mode validation) allow docker-compose-dev.yml too. To avoid sending users to the wrong file, consider referencing the detected compose file path (or saying “your compose file”) here.

This issue also appears on line 560 of the same file.

Suggested change
nextSteps := " Edit your docker-compose.yml to use different host ports, or stop the conflicting container."
nextSteps := " Edit your compose file to use different host ports, or stop the conflicting container."

Copilot uses AI. Check for mistakes.
Comment thread docs/status.md Outdated
| ❌ | Connection refused or timeout |

Grafana is checked at `/api/health`. Backend and Config UI are checked at their root URL.
Backend is checked at `/ping`, Grafana at `/api/health`, and Config UI at its root URL. When auto-discovery lands on `http://localhost:8080`, companion URLs infer to `http://localhost:3002` and `http://localhost:4000`. When it lands on `http://localhost:8085`, companion URLs infer to `http://localhost:3004` and `http://localhost:4004`.
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The sentence listing inferred companion URLs doesn’t indicate which URL is Grafana vs Config UI, which can be confusing (it just lists two URLs per bundle). Consider explicitly labeling them (e.g., “Grafana → …, Config UI → …”) for both the 8080 and 8085 cases.

Suggested change
Backend is checked at `/ping`, Grafana at `/api/health`, and Config UI at its root URL. When auto-discovery lands on `http://localhost:8080`, companion URLs infer to `http://localhost:3002` and `http://localhost:4000`. When it lands on `http://localhost:8085`, companion URLs infer to `http://localhost:3004` and `http://localhost:4004`.
Backend is checked at `/ping`, Grafana at `/api/health`, and Config UI at its root URL. When auto-discovery lands on `http://localhost:8080`, companion URLs infer to Grafana → `http://localhost:3002`, Config UI → `http://localhost:4000`. When it lands on `http://localhost:8085`, companion URLs infer to Grafana → `http://localhost:3004`, Config UI → `http://localhost:4004`.

Copilot uses AI. Check for mistakes.
Comment thread docs/deploy.md Outdated
5. Starts containers (unless `--start=false`)

**custom**:
1. Uses the existing `docker-compose.yml` (or `docker-compose-dev.yml`) in the target directory
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This section states that --source custom can use docker-compose-dev.yml, but the deploy code starts containers via docker compose up in the directory without selecting a compose file (no -f docker-compose-dev.yml). If the directory only contains docker-compose-dev.yml, deploy local will fail. Either document that custom requires docker-compose.yml, or update the implementation to use the detected compose file (e.g., pass -f or copy/rename dev → docker-compose.yml).

Suggested change
1. Uses the existing `docker-compose.yml` (or `docker-compose-dev.yml`) in the target directory
1. Uses the existing `docker-compose.yml` in the target directory (if you only have `docker-compose-dev.yml`, rename it to `docker-compose.yml` or start containers manually with `docker compose -f docker-compose-dev.yml up -d`)

Copilot uses AI. Check for mistakes.
@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 25, 2026

@claude[agent] apply changes based on the comments in this thread

- Use generic "compose file" instead of hardcoded "docker-compose.yml" in error messages
- Add explicit Grafana/Config UI labels to status docs URL mappings
- Clarify that custom mode requires docker-compose.yml (not docker-compose-dev.yml)

Agent-Logs-Url: https://github.com/DevExpGbb/gh-devlake/sessions/5b89b918-ab13-4890-82b6-7f25c5e85ae2

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented Mar 25, 2026

@claude[agent] apply changes based on the comments in this thread

All three review items addressed in afdb558:

  1. Generic compose file reference: Changed hardcoded "docker-compose.yml" to "compose file" in port conflict remediation text (line 511).

  2. Explicit URL labels: Added Grafana/Config UI labels to status.md companion URL mappings (e.g., "Grafana → http://localhost:3002, Config UI → http://localhost:4000").

  3. Custom mode compose file clarification: Updated docs/deploy.md to clarify that custom mode requires docker-compose.yml (not docker-compose-dev.yml). Added note with workaround for users who only have the dev variant.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Comment thread cmd/deploy_errors_test.go Outdated
Comment on lines +1 to +2
package cmd

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This file appears to have Windows CRLF line endings (line 1 shows a trailing \r in searches). Please convert to LF to match the repository’s standard line endings and avoid cross-platform issues.

Copilot uses AI. Check for mistakes.
Comment thread cmd/deploy_errors.go Outdated
func printDockerPortConflictError(de *DeployError, customHeader string, nextSteps string) {
// Print header
if customHeader != "" {
fmt.Println(customHeader)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

When customHeader is provided, it’s printed verbatim. To keep terminal output spacing consistent (blank line before emoji-prefixed steps), consider normalizing customHeader inside this function (e.g., prefixing a newline if missing) rather than relying on all callers to include \n correctly.

Suggested change
fmt.Println(customHeader)
normalizedHeader := customHeader
if !strings.HasPrefix(normalizedHeader, "\n") {
normalizedHeader = "\n" + normalizedHeader
}
fmt.Println(normalizedHeader)

Copilot uses AI. Check for mistakes.
Comment thread cmd/deploy_local.go Outdated
Comment on lines +685 to +688
if err := os.WriteFile(composePath, []byte(modified), 0644); err != nil {
return fmt.Errorf("writing compose file: %w", err)
}

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

rewriteComposePorts overwrites the user’s compose file in-place. Since this runs as part of an automated recovery path, consider writing a backup (e.g., docker-compose.yml.bak) and/or doing an atomic write (temp file + rename) so users can easily revert and to reduce risk of partial writes if the process is interrupted.

Suggested change
if err := os.WriteFile(composePath, []byte(modified), 0644); err != nil {
return fmt.Errorf("writing compose file: %w", err)
}
// Create a backup of the original compose file so users can revert if needed.
backupPath := composePath + ".bak"
if _, statErr := os.Stat(backupPath); os.IsNotExist(statErr) {
if err := os.WriteFile(backupPath, data, 0644); err != nil {
return fmt.Errorf("creating compose backup %s: %w", backupPath, err)
}
}
// Write updated content atomically via temp file + rename to avoid partial writes.
dir := filepath.Dir(composePath)
tmpFile, err := os.CreateTemp(dir, ".compose-*.tmp")
if err != nil {
return fmt.Errorf("creating temp compose file: %w", err)
}
// Ensure temp file is removed on failure paths.
defer os.Remove(tmpFile.Name())
if _, err := tmpFile.Write([]byte(modified)); err != nil {
tmpFile.Close()
return fmt.Errorf("writing temp compose file: %w", err)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("closing temp compose file: %w", err)
}
if err := os.Rename(tmpFile.Name(), composePath); err != nil {
return fmt.Errorf("replacing compose file atomically: %w", err)
}

Copilot uses AI. Check for mistakes.
Comment thread docs/deploy.md Outdated
Comment on lines 71 to 72
**Port Fallback**: When deploying with `--source official` or `--source fork`, the CLI automatically recovers from port conflicts by retrying with alternate ports (`8085/3004/4004`). Custom deployments require manual port conflict resolution.

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The doc mentions port fallback, but it doesn’t note that the CLI rewrites docker-compose.yml port mappings on disk as part of the recovery. Please document that side effect (what gets rewritten, and how to revert) so users aren’t surprised when their compose file changes.

Suggested change
**Port Fallback**: When deploying with `--source official` or `--source fork`, the CLI automatically recovers from port conflicts by retrying with alternate ports (`8085/3004/4004`). Custom deployments require manual port conflict resolution.
**Port Fallback**: When deploying with `--source official` or `--source fork`, the CLI automatically recovers from port conflicts by retrying with alternate ports (`8085/3004/4004`). As part of this recovery, it updates the port mappings in `docker-compose.yml` on disk so that future `docker compose up` runs use the fallback ports. Custom deployments (`--source custom`) are not modified automatically and require manual port conflict resolution.
To revert fallback changes, edit `docker-compose.yml` and restore your preferred host ports (for example, change the mapped ports back to `8080/3002/4000` if they were adjusted), or revert the file via version control / re-run the deployment in a clean directory.

Copilot uses AI. Check for mistakes.
Comment thread cmd/deploy_errors.go Outdated
Comment on lines +1 to +2
package cmd

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This file appears to have Windows CRLF line endings (e.g., lines show trailing \r in searches). The repo is predominantly LF; please convert this file to LF to avoid noisy diffs and potential tooling issues on Unix environments.

Copilot uses AI. Check for mistakes.
@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 25, 2026

@claude[agent] please address comments in this thread: #145 (review)

- Convert deploy_errors.go and deploy_errors_test.go from CRLF to LF
- Normalize customHeader in printDockerPortConflictError for consistent spacing
- Add backup (.bak) and atomic write (temp+rename) to rewriteComposePorts
- Document port rewrite side effect and backup file in deploy.md

Agent-Logs-Url: https://github.com/DevExpGbb/gh-devlake/sessions/cbca78e8-02a6-4a80-b698-12d2fb258ca9

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented Mar 25, 2026

@claude[agent] please address comments in this thread: #145 (review)

All five review items addressed in 78c70a3:

  1. CRLF to LF conversion: Converted both cmd/deploy_errors.go and cmd/deploy_errors_test.go from CRLF to LF line endings.

  2. Header normalization: Added logic in printDockerPortConflictError to normalize customHeader by prepending \n if missing, ensuring consistent spacing.

  3. Backup and atomic write: Updated rewriteComposePorts to create a backup at docker-compose.yml.bak (if it doesn't exist) and use atomic writes (temp file + rename) to avoid partial writes on interruption.

  4. Documentation: Updated docs/deploy.md to document that port fallback rewrites docker-compose.yml on disk, creates a backup, and explains how to revert changes.

All tests pass.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Comment thread cmd/deploy_local.go Outdated
Comment on lines +561 to +578
// Checks for docker-compose.yml first, then docker-compose-dev.yml.
func findComposeFile(dir string) string {
composePath := filepath.Join(dir, "docker-compose.yml")
if _, err := os.Stat(composePath); err == nil {
return composePath
}
return filepath.Join(dir, "docker-compose-dev.yml")
}

// waitAndDetectBackendURL polls the backend URL extracted from the compose file.
// Falls back to probing both 8080 and 8085 if extraction fails.
func waitAndDetectBackendURL(dir string) (string, error) {
composePath := findComposeFile(dir)

// Try to extract the actual backend port from the compose file
ports := extractServicePorts(composePath, "devlake")
var backendURLCandidates []string

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

findComposeFile falls back to docker-compose-dev.yml, but the deploy path starts containers via internal/docker.ComposeUp, which runs docker compose up -d without a -f flag (so it only uses docker-compose.yml / compose.yml defaults). If docker-compose.yml is missing, deploy will still fail to start even though findComposeFile returns a path. Consider either (a) requiring/ensuring docker-compose.yml exists before starting containers (copy/rename dev file), or (b) extending ComposeUp to accept a compose filename and use -f consistently across start + port extraction.

Suggested change
// Checks for docker-compose.yml first, then docker-compose-dev.yml.
func findComposeFile(dir string) string {
composePath := filepath.Join(dir, "docker-compose.yml")
if _, err := os.Stat(composePath); err == nil {
return composePath
}
return filepath.Join(dir, "docker-compose-dev.yml")
}
// waitAndDetectBackendURL polls the backend URL extracted from the compose file.
// Falls back to probing both 8080 and 8085 if extraction fails.
func waitAndDetectBackendURL(dir string) (string, error) {
composePath := findComposeFile(dir)
// Try to extract the actual backend port from the compose file
ports := extractServicePorts(composePath, "devlake")
var backendURLCandidates []string
// For local deploys, we only consider docker-compose.yml, because docker.ComposeUp
// runs "docker compose up" without a -f flag and therefore only uses default filenames.
// If docker-compose.yml is not present, this returns an empty string.
func findComposeFile(dir string) string {
composePath := filepath.Join(dir, "docker-compose.yml")
if _, err := os.Stat(composePath); err == nil {
return composePath
}
return ""
}
// waitAndDetectBackendURL polls the backend URL extracted from the compose file.
// Falls back to probing both 8080 and 8085 if extraction fails or no compose file is found.
func waitAndDetectBackendURL(dir string) (string, error) {
composePath := findComposeFile(dir)
var ports map[string]int
if composePath != "" {
// Try to extract the actual backend port from the compose file (when present)
ports = extractServicePorts(composePath, "devlake")
} else {
// No compose file detected that Docker would use; rely on default port probing
ports = map[string]int{}
}
var backendURLCandidates []string

Copilot uses AI. Check for mistakes.
Comment thread docs/deploy.md

### Notes

- If `.env` already exists in the target directory, it is backed up to `.env.bak` before being replaced.
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

The note about always backing up .env to .env.bak before replacement doesn’t match current behavior across all --source modes: fork/custom flows can write/update .env (e.g., ENCRYPTION_SECRET injection) without creating a backup, and fork cloning can overwrite an existing .env. Consider tightening the doc to the specific cases where a backup is actually created, or updating the implementation to consistently back up .env before any overwrite.

Suggested change
- If `.env` already exists in the target directory, it is backed up to `.env.bak` before being replaced.
- Deployments may overwrite an existing `.env` without creating a `.env.bak` backup — back up your `.env` manually if you have important local changes.

Copilot uses AI. Check for mistakes.
@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 30, 2026

@claude Please address these review comments:

  1. docs/deploy.md:23 — The --start flag is documented as defaulting to true, but the surrounding sections still instruct users to run docker compose up -d manually. Reconcile the docs so they reflect auto-start by default.

  2. docs/deploy.md:56 — The section header/intro still frames deploy-local as primarily preparing files for manual startup. Update the narrative to reflect that containers start automatically by default and --start=false is the opt-out.

@ewega
Copy link
Copy Markdown
Contributor

ewega commented Mar 30, 2026

@claude[agent] Please address these review comments:

  1. docs/deploy.md:23 — The --start flag is documented as defaulting to true, but the surrounding sections still instruct users to run docker compose up -d manually. Reconcile the docs so they reflect auto-start by default.

  2. docs/deploy.md:56 — The section header/intro still frames deploy-local as primarily preparing files for manual startup. Update the narrative to reflect that containers start automatically by default and --start=false is the opt-out.

Restructured "After Running" section to lead with the default
auto-start behavior. Manual docker compose up -d is now clearly
framed as the exception case when using --start=false.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented Mar 30, 2026

@claude Please address these review comments:

  1. docs/deploy.md:23 — The --start flag is documented as defaulting to true, but the surrounding sections still instruct users to run `docker...

Fixed in 30e443a. The "After Running" section now leads with the default auto-start behavior, and manual docker compose up -d is clearly framed as the exception case for --start=false.

@claude[agent] Please address these review comments:

  1. docs/deploy.md:23 — The --start flag is documented as defaulting to true, but the surrounding sections still instruct users to run ...

Fixed in 30e443a. The "After Running" section now leads with the default auto-start behavior, and manual docker compose up -d is clearly framed as the exception case for --start=false.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment thread cmd/deploy_local.go
Comment on lines +693 to +713
// Write updated content atomically via temp file + rename to avoid partial writes
dir := filepath.Dir(composePath)
tmpFile, err := os.CreateTemp(dir, ".compose-*.tmp")
if err != nil {
return fmt.Errorf("creating temp compose file: %w", err)
}
// Ensure temp file is removed on failure paths
defer os.Remove(tmpFile.Name())

if _, err := tmpFile.Write([]byte(modified)); err != nil {
tmpFile.Close()
return fmt.Errorf("writing temp compose file: %w", err)
}

if err := tmpFile.Close(); err != nil {
return fmt.Errorf("closing temp compose file: %w", err)
}

if err := os.Rename(tmpFile.Name(), composePath); err != nil {
return fmt.Errorf("replacing compose file atomically: %w", err)
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

rewriteComposePorts writes the updated compose content via os.CreateTemp + os.Rename, but os.CreateTemp creates the temp file with 0600 permissions. After rename, docker-compose.yml will inherit those permissions, potentially changing the original file mode unexpectedly. Preserve the original compose file permissions (e.g., stat the existing file and chmod the temp file to the same mode before renaming, or write the temp file with the original perms).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

cmd/deploy_local.go:21

  • Import grouping/order doesn’t match the repo convention (stdlib → external → internal with blank lines). github.com/spf13/cobra is currently grouped after internal imports; move it into the external section above the internal packages.
import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/DevExpGBB/gh-devlake/internal/devlake"
	dockerpkg "github.com/DevExpGBB/gh-devlake/internal/docker"
	"github.com/DevExpGBB/gh-devlake/internal/download"
	"github.com/DevExpGBB/gh-devlake/internal/gitclone"
	"github.com/DevExpGBB/gh-devlake/internal/prompt"
	"github.com/DevExpGBB/gh-devlake/internal/secrets"
	"github.com/spf13/cobra"
)

Comment thread cmd/deploy_local.go Outdated
// Not a port conflict or unknown error - print general cleanup and fail
fmt.Println("\n💡 To clean up partial artifacts:")
fmt.Println(" gh devlake cleanup --local --force")
return "", fmt.Errorf("docker compose up failed: %w", err)
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

This error path wraps an error that already includes the prefix "docker compose up failed", which can lead to duplicated text like "docker compose up failed: docker compose up failed: ...". Consider changing the wrapper context (e.g., "starting containers"/"running docker compose") or returning err directly here.

Suggested change
return "", fmt.Errorf("docker compose up failed: %w", err)
return "", fmt.Errorf("starting containers with docker compose: %w", err)

Copilot uses AI. Check for mistakes.
Comment thread cmd/deploy_local.go Outdated
Comment on lines +560 to +568
// findComposeFile returns the path to the active docker compose file in the given directory.
// Checks for docker-compose.yml first, then docker-compose-dev.yml.
func findComposeFile(dir string) string {
composePath := filepath.Join(dir, "docker-compose.yml")
if _, err := os.Stat(composePath); err == nil {
return composePath
}
return filepath.Join(dir, "docker-compose-dev.yml")
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

findComposeFile is documented as returning the “active” compose file, but dockerpkg.ComposeUp runs docker compose up without a -f flag (so only the default compose filename is truly “active”). Consider updating the comment/name to clarify this is for introspection (ports/UX output) and that docker-compose-dev.yml may not be used by docker compose up unless explicitly specified.

Copilot uses AI. Check for mistakes.
Comment thread README.md Outdated
```

After setup, open Grafana at **http://localhost:3002** (admin / admin). DORA and Copilot dashboards will populate after the first sync completes.
After setup, open the URL bundle printed by `gh devlake deploy local`. Local deploys normally use `8080/4000/3002`, and automatically fall back once to `8085/4004/3004` when the default ports are already in use. DORA and Copilot dashboards will populate after the first sync completes.
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

The port bundle ordering here (8080/4000/3002 and 8085/4004/3004) is inconsistent with other docs/help text that use 8080/3002/4000 and 8085/3004/4004. Consider using the same ordering everywhere to reduce user confusion.

Suggested change
After setup, open the URL bundle printed by `gh devlake deploy local`. Local deploys normally use `8080/4000/3002`, and automatically fall back once to `8085/4004/3004` when the default ports are already in use. DORA and Copilot dashboards will populate after the first sync completes.
After setup, open the URL bundle printed by `gh devlake deploy local`. Local deploys normally use `8080/3002/4000`, and automatically fall back once to `8085/3004/4004` when the default ports are already in use. DORA and Copilot dashboards will populate after the first sync completes.

Copilot uses AI. Check for mistakes.
@ewega
Copy link
Copy Markdown
Contributor

ewega commented May 21, 2026

@claude Please address the Code Review Agent findings from the latest review cycle:

  • Finding: cmd/deploy_local.go wraps an error already prefixed docker compose up failed, producing docker compose up failed: docker compose up failed: ....
    • Requested change: Drop the redundant prefix in the wrap.
  • Finding: findComposeFile is documented as returning the "active" compose file, but dockerpkg.ComposeUp runs docker compose up without a -f flag, so only the default compose filename is truly active.
    • Requested change: Either pass -f through ComposeUp or update the doc/code so the "active" semantics match runtime behavior.
  • Finding: Port bundle ordering in README (8080/4000/3002 and 8085/4004/3004) is inconsistent with other docs/help text that use 8080/3002/4000 / 8085/3004/4004.
    • Requested change: Normalize ordering across README, docs, and help text.

Validation: go build ./..., go vet ./..., go test ./....

@ewega
Copy link
Copy Markdown
Contributor

ewega commented May 21, 2026

@claude[agent] Please address the Code Review Agent findings from the latest review cycle on this PR:

  • Finding: cmd/deploy_local.go wraps an error already prefixed docker compose up failed, producing docker compose up failed: docker compose up failed: ....
    • Requested change: Drop the redundant prefix in the wrap.
  • Finding: findComposeFile is documented as returning the "active" compose file, but dockerpkg.ComposeUp runs docker compose up without a -f flag, so only the default compose filename is truly active.
    • Requested change: Either pass -f through ComposeUp or update the doc/code so semantics match runtime behavior.
  • Finding: Port bundle ordering in README (8080/4000/3002 and 8085/4004/3004) is inconsistent with other docs/help text that use 8080/3002/4000 / 8085/3004/4004.
    • Requested change: Normalize ordering across README, docs, and help text.

Validation: go build ./..., go vet ./..., go test ./....

@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented May 21, 2026

@claude Please address the Code Review Agent findings from the latest review cycle:

  • Finding: cmd/deploy_local.go wraps an error already prefixed docker compose up failed, producing `docker ...

All three findings addressed in 5c40e3a:

  1. Error wrapping: Changed line 469 from fmt.Errorf("docker compose up failed: %w", err) to fmt.Errorf("starting containers: %w", err) to avoid duplicate "docker compose up failed" prefixes.

  2. Compose file semantics: Updated findComposeFile docstring to clarify it's for introspection only. The function returns docker-compose-dev.yml as a fallback for port extraction/UX, but ComposeUp only uses the default docker-compose.yml.

  3. Port ordering: Fixed README line 59 from 8080/4000/3002 and 8085/4004/3004 to 8080/3002/4000 and 8085/3004/4004 to match docs/help text ordering (backend/grafana/configui).

@claude[agent] Please address the Code Review Agent findings from the latest review cycle on this PR:

  • Finding: cmd/deploy_local.go wraps an error already prefixed docker compose up failed, ...

All three findings addressed in 5c40e3a:

  1. Error wrapping: Changed line 469 from fmt.Errorf("docker compose up failed: %w", err) to fmt.Errorf("starting containers: %w", err) to avoid duplicate "docker compose up failed" prefixes.

  2. Compose file semantics: Updated findComposeFile docstring to clarify it's for introspection only. The function returns docker-compose-dev.yml as a fallback for port extraction/UX, but ComposeUp only uses the default docker-compose.yml.

  3. Port ordering: Fixed README line 59 from 8080/4000/3002 and 8085/4004/3004 to 8080/3002/4000 and 8085/3004/4004 to match docs/help text ordering (backend/grafana/configui).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 9/9 changed files
  • Comments generated: 2

Comment thread cmd/deploy_local.go Outdated
Comment on lines +561 to +569
// Checks for docker-compose.yml first, then docker-compose-dev.yml.
// Note: docker.ComposeUp runs "docker compose up" without -f, so only docker-compose.yml
// (or compose.yml) is used at runtime. This function is for port extraction and UX output.
func findComposeFile(dir string) string {
composePath := filepath.Join(dir, "docker-compose.yml")
if _, err := os.Stat(composePath); err == nil {
return composePath
}
return filepath.Join(dir, "docker-compose-dev.yml")
Comment thread docs/deploy.md Outdated
Comment on lines +45 to +50
1. Uses the existing `docker-compose.yml` in the target directory
2. Generates or updates `.env` with `ENCRYPTION_SECRET` if needed
3. Checks that Docker is available
4. Starts containers (unless `--start=false`)

**Note**: The compose file must be named `docker-compose.yml`. If you only have `docker-compose-dev.yml`, either rename it or start containers manually with `docker compose -f docker-compose-dev.yml up -d`.
@ewega
Copy link
Copy Markdown
Contributor

ewega commented May 22, 2026

@claude[agent] Please address the Code Review Agent findings from the latest review cycle:

  • Finding: findComposeFile in cmd/deploy_local.go only checks docker-compose.yml and docker-compose-dev.yml, but Docker Compose also auto-detects compose.yml / compose.yaml / docker-compose.yaml. Port-bundle detection and rewrite will operate on the wrong file for users with a compose.yml.
    • Requested change: Check the full set of default Compose filenames (compose.yml, compose.yaml, docker-compose.yml, docker-compose.yaml) and reserve docker-compose-dev.yml for known dev-mode introspection.
  • Finding: docs/deploy.md documents only docker-compose.yml for custom deployments.
    • Requested change: Document the full set of supported default Compose filenames and clarify which ones the CLI can auto-modify for port fallback.

Validation: go build ./..., go vet ./..., go test ./....

- findComposeFile now checks compose.yaml, compose.yml, docker-compose.yaml,
  docker-compose.yml (in Docker's auto-detection order), plus docker-compose-dev.yml
  as a dev fallback for introspection
- Port detection, rewriting, and URL inference now work correctly for users with
  compose.yml or other standard filenames
- Added error handling when no compose file is found during port conflict recovery
- Updated docs/deploy.md to document all supported compose filenames and clarify
  which ones can be auto-modified for port fallback
- Updated docs/start.md and docs/stop.md to reflect compose file auto-detection order
- Added comprehensive test coverage for findComposeFile precedence rules

Agent-Logs-Url: https://github.com/DevExpGbb/gh-devlake/sessions/d29dab8a-3634-4691-af39-92a2ff04e77e

Co-authored-by: ewega <26189114+ewega@users.noreply.github.com>
@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented May 22, 2026

@claude[agent] Please address the Code Review Agent findings from the latest review cycle:

  • Finding: findComposeFile in cmd/deploy_local.go only checks docker-compose.yml and `docker-compo...

Both findings addressed in 5109932:

  1. Full compose filename detection: findComposeFile now checks all Docker Compose default filenames in the correct precedence order (compose.yamlcompose.ymldocker-compose.yamldocker-compose.yml), plus docker-compose-dev.yml as a dev fallback for introspection. Port detection, rewriting, and URL inference work correctly for all standard filenames.

  2. Documentation updated: docs/deploy.md now documents all supported compose filenames and clarifies which ones can be auto-modified for port fallback. Also updated docs/start.md and docs/stop.md to reflect the auto-detection order.

Added comprehensive test coverage for filename precedence. All validation passes.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 11/11 changed files
  • Comments generated: 5

Comment thread cmd/deploy_local.go
Comment on lines 3 to 8
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
Comment thread cmd/deploy_local.go Outdated
Comment on lines +739 to +741
if err := os.Rename(tmpFile.Name(), composePath); err != nil {
return fmt.Errorf("replacing compose file atomically: %w", err)
}
Comment thread cmd/deploy_local.go Outdated
Comment on lines +566 to +577
// Checks for compose files in the order Docker Compose auto-detects them:
// compose.yaml → compose.yml → docker-compose.yaml → docker-compose.yml → docker-compose-dev.yml (dev fallback).
// Note: docker.ComposeUp runs "docker compose up" without -f, so it uses Docker's default
// auto-detection order. This function is for port extraction, port rewriting, and UX output.
func findComposeFile(dir string) string {
// Check in the same order Docker Compose auto-detects compose files
candidates := []string{
"compose.yaml",
"compose.yml",
"docker-compose.yaml",
"docker-compose.yml",
"docker-compose-dev.yml", // Dev fallback for introspection
Comment thread docs/stop.md
2. `.devlake-azure.json` → Azure mode
3. `.devlake-local.json` → Local mode
4. `docker-compose.yml` in current directory → Local mode
4. Standard compose file in current directory → Local mode (searches for `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml` in that order)
Comment thread docs/start.md
2. `.devlake-azure.json` → Azure mode
3. `.devlake-local.json` → Local mode
4. `docker-compose.yml` in current directory → Local mode
4. Standard compose file in current directory → Local mode (searches for `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml` in that order)
@ewega
Copy link
Copy Markdown
Contributor

ewega commented May 22, 2026

@claude[agent] Round 2 — Code Review Agent found new issues introduced by the previous fix:

  • Finding (blocking): Import block in cmd/deploy_local.go no longer follows repo grouping (stdlib → external → internal, blank lines between).
    • Requested change: Reorder so github.com/spf13/cobra and other external deps are grouped, internal/... imports get their own group.
  • Finding (blocking): os.Rename(tmp, composePath) is not a portable atomic replace — on Windows, os.Rename fails when the destination exists, breaking the port-fallback rewrite on Windows (a primary target of this CLI).
    • Requested change: Use a cross-platform replace strategy — remove the destination first or use os.Remove + os.Rename with proper handling, or write directly with 0644 perms.
  • Finding (blocking — doc/code drift): docs/stop.md documents auto-detection of compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml, but cmd/stop.go (~lines 102–105) only checks docker-compose.yml.
    • Requested change: Either implement the documented set in detectStopMode, or revert the docs to match the actual behavior. Prefer extending the code for consistency with the new findComposeFile set.
  • Finding (blocking — same drift): Same for docs/start.md vs cmd/start.go detectStartMode.
    • Requested change: Same — align code with docs (preferred) or vice versa.
  • Finding (suggestion): Comment in cmd/deploy_local.go says the candidate list is the order Docker Compose auto-detects, but docker-compose-dev.yml is a CLI-only fallback — not in Docker's set.
    • Requested change: Clarify in the comment that docker-compose-dev.yml is a CLI-only introspection fallback.

Validation: go build ./..., go vet ./..., go test ./.... Manual: try on a project with only compose.yml to verify detection.

@Claude
Copy link
Copy Markdown
Contributor Author

Claude AI commented May 22, 2026

@claude[agent] Round 2 — Code Review Agent found new issues introduced by the previous fix:

  • Finding (blocking): Import block in cmd/deploy_local.go no longer follows repo grouping (stdlib �...

All five findings addressed in 669efe4:

  1. Import grouping: Reordered imports to follow repo convention (stdlib → external → internal, blank lines between).

  2. Windows atomicity: Fixed rewriteComposePorts to preserve original file permissions and use os.Remove + os.Rename for cross-platform atomic replace (Windows os.Rename fails when destination exists).

  3. detectStopMode alignment: Extended to check all Docker Compose default filenames (compose.yaml, compose.yml, docker-compose.yaml, docker-compose.yml) in precedence order, matching the documented behavior.

  4. detectStartMode alignment: Same — now checks all standard compose filenames, matching docs.

  5. Comment clarification: Updated findComposeFile docstring to explicitly call out docker-compose-dev.yml as a CLI-only introspection fallback, not part of Docker's auto-detection set.

All validation passed (go build, go vet, go test).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 13/13 changed files
  • Comments generated: 3

Comment thread cmd/deploy_local.go
Comment on lines +817 to +821
func inferCompanionURLs(backendURL string, composePath string) (grafanaURL, configUIURL string) {
// Try to extract actual ports from compose file
ports := extractServicePorts(composePath, "grafana", "config-ui")

if grafanaPort, ok := ports["grafana"]; ok {
Comment thread docs/deploy.md Outdated
Comment thread cmd/deploy_local.go
Comment on lines +754 to +762
// On Windows, os.Rename fails if destination exists, so remove it first
if err := os.Remove(composePath); err != nil {
return fmt.Errorf("removing original compose file: %w", err)
}

if err := os.Rename(tmpPath, composePath); err != nil {
return fmt.Errorf("replacing compose file atomically: %w", err)
}

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.

Improve deploy-time error classification and bounded recovery

4 participants