Skip to content

fix(3686): target type select when using stack - #3967

Closed
arkhoss wants to merge 1 commit into
getarcaneapp:mainfrom
arkhoss:fix/3686-target-type-select
Closed

arkhoss wants to merge 1 commit into
getarcaneapp:mainfrom
arkhoss:fix/3686-target-type-select

Conversation

@arkhoss

@arkhoss arkhoss commented Sep 15, 2026 •

Copy link
Copy Markdown

Checklist

  • This PR is not opened from my fork’s main branch
  • All new user-facing strings are translated via Paraglide (m.*())

What This PR Implements

Fixes: #3686

Changes Made

Frontend

[gitops-sync-dialog.svelte]

  • Fixed formData derivation bug where changing selectedTargetType caused formData.composePath to re-derive and recreate the form state (resetting the selection back to "Project").
  • Updated composePath derivation to check normalizeTargetType(targetType) instead of selectedTargetType.
  • Updated TargetType onValueChange handler in SelectWithLabel to update selectedTargetType and set default compose file path (compose.yml for swarm_stack, docker-compose.yml for project).

[swarm.ts]

  • Added targetType?: string; to frontend Project interface.
  • Added targetType column and TargetTypeCell badge renderer in projects-table.svelte to display deployment target type ("Swarm Stack" or "Compose").

Backend & Types

[project.go]

  • Added TargetType string field to Project database model and Details DTO.

[gitops_sync.go]

  • Normalized req.TargetType on sync creation ("stack", "swarm", "swarm_stack" -> "swarm_stack").
  • Implemented upsertSwarmStackProjectRecordInternal to find or create the Project record in the database with target_type = "swarm_stack", gitops_managed_by = &sync.ID, and link sync.ProjectID.
  • Invoked upsertSwarmStackProjectRecordInternal during CreateSync, UpdateSync, and performSwarmStackSyncInternal.
  • Executed Swarm stack deployment via swarmService.DeployStack during performSwarmStackSyncInternal.

[project_listing.go] [project_sync.go]

  • Added reconcileSwarmStackProjectsInternal to reconcile and list Swarm stack project records in project listings (ListAllProjects, ListProjects, GetProjectStatusCounts).
  • Preserved status for swarm_stack projects when active container count is 0.
  • Excluded swarm_stack projects from filesystem cleanup in skipProjectCleanupInternal.

Testing Done

PATH=$PATH:/usr/local/go/bin:$HOME/go/bin go test ./backend/... > backend-test.dat

backend-test.log

PATH=$PATH:/usr/local/go/bin:$HOME/go/bin go test ./backend/internal/gitops/... ./backend/internal/project/... > target-domain.log

target-domain.log

PATH=$PATH:/usr/local/go/bin:$HOME/go/bin go test ./cli/...

cli-test.log

PATH=$PATH:/usr/local/go/bin:$HOME/go/bin go test ./types/... > types-test.log

type-test.log

cd frontend && npx pnpm check                                            *[fix/3686-target-type-select]  ➜  0c0185bb    3.3 38.3GiB 

$ pnpm paraglide:compile && svelte-kit sync && svelte-check-rs --tsconfig ./tsconfig.json
$ paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide --strategy cookie preferredLanguage baseLocale --silent
The `config.alias` option is deprecated, and will be removed in a future version of SvelteKit. Use subpath imports instead: https://svelte.dev/docs/kit/$lib
svelte-check-rs found 0 errors and 0 warnings

AI Tool Used (if applicable)

Used Google Antigravity with model Gemini 3.6

Additional Context

Also tested manually, bring it up using

./scripts/development/dev.sh start

Selected Environments > Git Syncs > Add Sync > Target Type > Stack

image

Stack created:

image

Disclaimer Greptiles Reviews use AI, make sure to check over its work.

To better help train Greptile on our codebase, if the comment is useful and valid Like the comment, if its not helpful or invalid Dislike

To have Greptile Re-Review the changes, mention greptileai.

Greptile Summary

This PR adds persisted and API-visible deployment target types, creates project records for GitOps-managed Swarm stacks, reconciles those records into project listings, and updates the GitOps dialog and project table.

  • Adds Swarm-stack project creation, binding, reconciliation, deployment, and cleanup behavior.
  • Exposes target type through the shared project DTO and frontend model.
  • Updates compose-path defaults when the target selector changes.
  • Adds a target-type badge column to the projects table.

Confidence Score: 0/5

This PR is not safe to merge because stack reconciliation can take over unrelated same-named projects, update and deletion transitions leave orphaned records, status is inaccurate, and database failures can panic the sync path.

The new stack-project lifecycle does not preserve unique two-way ownership: name-only lookup can repoint existing projects, updates operate on stale sync state, deleted bindings are never cleaned up, and failed upserts are either discarded or followed by a nil dereference. Stack status is also initialized and retained as running without consulting the Swarm runtime.

Files Needing Attention: backend/internal/gitops/gitops_sync.go, backend/internal/project/project_listing.go, backend/internal/project/project_sync.go, backend/internal/project/model.go, frontend/src/routes/(app)/projects/projects-table.svelte

Fix all with Greploop Fix All in Codex Fix All in Claude Code Fix All in Cursor

Prompt To Fix All With AI
### Issue 1
backend/internal/gitops/gitops_sync.go:1193
**Project ownership can be overwritten**

Project names are not unique, and stack paths are scoped by environment, but this lookup uses only `sync.ProjectName`. If an ordinary project or a stack in another environment has the same name, this code overwrites that project’s path, target type, and GitOps owner, then binds the new sync to it. The listing reconciliation at `backend/internal/project/project_listing.go:86-109` repeats the same name fallback, allowing one sync to take control of an unrelated project.

### Issue 2
backend/internal/gitops/gitops_sync.go:1157-1168
**Failed upsert can panic**

If project creation fails, `upsertSwarmStackProjectRecordInternal` returns a nil project and an error. This path only logs the error and then passes that nil project to `logSyncSuccess`, which dereferences `project.Name` and panics after the stack was deployed. The helper also discards the project and sync binding update errors at lines 1215 and 1220, and listing reconciliation does the same at `backend/internal/project/project_listing.go:95-109`. This violates the repository directive to handle all errors explicitly and can report a successful sync with a broken two-way binding.

### Issue 3
backend/internal/gitops/gitops_sync.go:821-825
**Upsert uses stale sync state**

`UpdateSync` writes the new target type and project name through an update map but does not refresh `syncRecord` before this check. Changing a project sync to a Swarm stack therefore skips the upsert, while changing a stack to a project or renaming it upserts the old target and name. This leaves the synthetic project bound to obsolete configuration.

### Issue 4
backend/internal/gitops/gitops_sync.go:1201
**Stack status remains falsely running**

This initializes the synthetic project as `Running` before the first deployment. Deployment failures do not update that value, and the listing paths at `backend/internal/project/project_listing.go:210-218` and `backend/internal/project/project_listing.go:990-997` preserve it whenever no Compose containers are found instead of deriving status from Swarm services or tasks. An initial deployment failure or a subsequently removed or stopped stack will therefore remain displayed and counted as running indefinitely.

### Issue 5
backend/internal/project/project_sync.go:358-360
**Orphaned stack projects persist**

This unconditional cleanup exemption also applies after a stack sync is deleted or converted to a regular project. Deleting a sync clears only `gitops_managed_by`, while reconciliation processes only current stack syncs. The orphaned project row therefore remains visible without an owner or backing stack and can never be removed by normal filesystem cleanup.

### Issue 6
frontend/src/routes/(app)/projects/projects-table.svelte:183
**Target sorting is unsupported**

The column is marked sortable, but neither backend listing path supports `targetType`: the database model lacks the required sortable tag, and the derived-filter configuration has no matching sort binding. Clicking this column therefore falls back to unrelated ordering instead of sorting projects by target type.

### Issue 7
frontend/src/routes/(app)/projects/projects-table.svelte:291
**Badge labels bypass localization**

`Swarm Stack` and `Compose` are new user-facing strings, but they are hard-coded instead of using Paraglide messages. Users in non-English locales will therefore see untranslated badge labels.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(3686): target type select when using..." | Re-trigger Greptile

Greptile also left 7 inline comments on this PR.

Context used (4)

stackSourcesDir := s.settingsService.GetStringSetting(ctx, "swarmStackSourcesDirectory", "/app/data/swarm/sources")
stackPath := filepath.Join(stackSourcesDir, sync.EnvironmentID, sync.ProjectName)

err := s.db.WithContext(ctx).Where("name = ?", sync.ProjectName).First(&proj).Error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Project ownership can be overwritten

Project names are not unique, and stack paths are scoped by environment, but this lookup uses only sync.ProjectName. If an ordinary project or a stack in another environment has the same name, this code overwrites that project’s path, target type, and GitOps owner, then binds the new sync to it. The listing reconciliation at backend/internal/project/project_listing.go:86-109 repeats the same name fallback, allowing one sync to take control of an unrelated project.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/gitops/gitops_sync.go
Line: 1193

Comment:
**Project ownership can be overwritten**

Project names are not unique, and stack paths are scoped by environment, but this lookup uses only `sync.ProjectName`. If an ordinary project or a stack in another environment has the same name, this code overwrites that project’s path, target type, and GitOps owner, then binds the new sync to it. The listing reconciliation at `backend/internal/project/project_listing.go:86-109` repeats the same name fallback, allowing one sync to take control of an unrelated project.

**Knowledge Base Used:**
- [GitOps and project automation](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/gitops-and-project-automation.md)
- [Projects, repositories, and buildables](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/projects-repositories-and-buildables.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor

Comment on lines +1157 to +1168
project, err := s.upsertSwarmStackProjectRecordInternal(ctx, sync)
if err != nil {
slog.WarnContext(ctx, "Failed to upsert Swarm stack project record", "stackName", sync.ProjectName, "error", err)
}

if len(syncedFiles) == 0 {
syncedFiles = singleFileSyncedFilesInternal(sync, source)
}
s.updateSyncStatusWithFiles(ctx, id, "success", "", source.commitHash, syncedFiles)
result.Success = true
result.Message = fmt.Sprintf("Successfully deployed swarm stack %s from %s", sync.ProjectName, sync.ComposePath)
s.logSyncSuccess(ctx, sync, project, actor)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Failed upsert can panic

If project creation fails, upsertSwarmStackProjectRecordInternal returns a nil project and an error. This path only logs the error and then passes that nil project to logSyncSuccess, which dereferences project.Name and panics after the stack was deployed. The helper also discards the project and sync binding update errors at lines 1215 and 1220, and listing reconciliation does the same at backend/internal/project/project_listing.go:95-109. This violates the repository directive to handle all errors explicitly and can report a successful sync with a broken two-way binding.

Rule Used: # Golang Pro Senior Go developer with deep expertise in Go 1.21+, concurrent programming, and cloud-native microservices. Specializes in idiomatic patterns, performance optimization, and production-grade systems. ## Role Definition You are a senio... (source)

Knowledge Base Used: GitOps and project automation

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/gitops/gitops_sync.go
Line: 1157-1168

Comment:
**Failed upsert can panic**

If project creation fails, `upsertSwarmStackProjectRecordInternal` returns a nil project and an error. This path only logs the error and then passes that nil project to `logSyncSuccess`, which dereferences `project.Name` and panics after the stack was deployed. The helper also discards the project and sync binding update errors at lines 1215 and 1220, and listing reconciliation does the same at `backend/internal/project/project_listing.go:95-109`. This violates the repository directive to handle all errors explicitly and can report a successful sync with a broken two-way binding.

**Rule Used:** # Golang Pro  Senior Go developer with deep expertise in Go 1.21+, concurrent programming, and cloud-native microservices. Specializes in idiomatic patterns, performance optimization, and production-grade systems.  ## Role Definition  You are a senio... ([source](https://app.greptile.com/ofkm/-/custom-context?memory=214b40a8-9695-4738-986d-5949b5d65ff1))

**Knowledge Base Used:** [GitOps and project automation](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/gitops-and-project-automation.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor

Comment on lines +821 to +825
if syncRecord.TargetType == "swarm_stack" {
if _, err := s.upsertSwarmStackProjectRecordInternal(ctx, syncRecord); err != nil {
slog.WarnContext(ctx, "Failed to upsert Swarm stack project record during UpdateSync", "error", err)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Upsert uses stale sync state

UpdateSync writes the new target type and project name through an update map but does not refresh syncRecord before this check. Changing a project sync to a Swarm stack therefore skips the upsert, while changing a stack to a project or renaming it upserts the old target and name. This leaves the synthetic project bound to obsolete configuration.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/gitops/gitops_sync.go
Line: 821-825

Comment:
**Upsert uses stale sync state**

`UpdateSync` writes the new target type and project name through an update map but does not refresh `syncRecord` before this check. Changing a project sync to a Swarm stack therefore skips the upsert, while changing a stack to a project or renaming it upserts the old target and name. This leaves the synthetic project bound to obsolete configuration.

**Knowledge Base Used:**
- [GitOps and project automation](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/gitops-and-project-automation.md)
- [Projects, repositories, and buildables](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/projects-repositories-and-buildables.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor

Path: stackPath,
TargetType: "swarm_stack",
GitOpsManagedBy: &sync.ID,
Status: projectpkg.ProjectStatusRunning,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Stack status remains falsely running

This initializes the synthetic project as Running before the first deployment. Deployment failures do not update that value, and the listing paths at backend/internal/project/project_listing.go:210-218 and backend/internal/project/project_listing.go:990-997 preserve it whenever no Compose containers are found instead of deriving status from Swarm services or tasks. An initial deployment failure or a subsequently removed or stopped stack will therefore remain displayed and counted as running indefinitely.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/gitops/gitops_sync.go
Line: 1201

Comment:
**Stack status remains falsely running**

This initializes the synthetic project as `Running` before the first deployment. Deployment failures do not update that value, and the listing paths at `backend/internal/project/project_listing.go:210-218` and `backend/internal/project/project_listing.go:990-997` preserve it whenever no Compose containers are found instead of deriving status from Swarm services or tasks. An initial deployment failure or a subsequently removed or stopped stack will therefore remain displayed and counted as running indefinitely.

**Knowledge Base Used:**
- [GitOps and project automation](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/gitops-and-project-automation.md)
- [Projects, repositories, and buildables](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/projects-repositories-and-buildables.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor

Comment on lines +358 to +360
if p.TargetType == "swarm_stack" {
return true
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Orphaned stack projects persist

This unconditional cleanup exemption also applies after a stack sync is deleted or converted to a regular project. Deleting a sync clears only gitops_managed_by, while reconciliation processes only current stack syncs. The orphaned project row therefore remains visible without an owner or backing stack and can never be removed by normal filesystem cleanup.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/internal/project/project_sync.go
Line: 358-360

Comment:
**Orphaned stack projects persist**

This unconditional cleanup exemption also applies after a stack sync is deleted or converted to a regular project. Deleting a sync clears only `gitops_managed_by`, while reconciliation processes only current stack syncs. The orphaned project row therefore remains visible without an owner or backing stack and can never be removed by normal filesystem cleanup.

**Knowledge Base Used:**
- [GitOps and project automation](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/gitops-and-project-automation.md)
- [Projects, repositories, and buildables](https://app.greptile.com/ofkm/-/custom-context/knowledge-base/getarcaneapp/arcane/-/docs/projects-repositories-and-buildables.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor

filterOptions: tagCatalog.map((tag) => ({ label: tag.name, value: tag.name }))
},
{ accessorKey: 'path', title: m.common_working_directory(), sortable: true, cell: DirectoryCell },
{ accessorKey: 'targetType', title: m.target_type(), sortable: true, cell: TargetTypeCell },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Target sorting is unsupported

The column is marked sortable, but neither backend listing path supports targetType: the database model lacks the required sortable tag, and the derived-filter configuration has no matching sort binding. Clicking this column therefore falls back to unrelated ordering instead of sorting projects by target type.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/routes/(app)/projects/projects-table.svelte
Line: 183

Comment:
**Target sorting is unsupported**

The column is marked sortable, but neither backend listing path supports `targetType`: the database model lacks the required sortable tag, and the derived-filter configuration has no matching sort binding. Clicking this column therefore falls back to unrelated ordering instead of sorting projects by target type.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code Fix in Cursor


{#snippet TargetTypeCell({ item }: { item: Project })}
<Badge variant="outline" class="font-normal capitalize">
{item.targetType === 'swarm_stack' ? 'Swarm Stack' : 'Compose'}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge labels bypass localization

Swarm Stack and Compose are new user-facing strings, but they are hard-coded instead of using Paraglide messages. Users in non-English locales will therefore see untranslated badge labels.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/routes/(app)/projects/projects-table.svelte
Line: 291

Comment:
**Badge labels bypass localization**

`Swarm Stack` and `Compose` are new user-facing strings, but they are hard-coded instead of using Paraglide messages. Users in non-English locales will therefore see untranslated badge labels.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code Fix in Cursor

@arcane-github-automation

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts. Please resolve the conflicts so the PR can stay up-to-date and reviewed.

@kmendell kmendell closed this Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐞 Bug: Bug in Git Sync Target Type select

2 participants