Skip to content

feat(repos): make repos install/uninstall the single path for GitLab - #5846

Closed
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:gitlab/cli-phase4
Closed

feat(repos): make repos install/uninstall the single path for GitLab#5846
ggallen wants to merge 1 commit into
fullsend-ai:mainfrom
ggallen:gitlab/cli-phase4

Conversation

@ggallen

@ggallen ggallen commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary

Wire GitLab-specific install and uninstall steps into repos management commands, eliminating the need for separate admin gitlab commands. This is Phase 4 of the GitLab support rollout.

Install (repos install)

  • Create project access token (Premium/Ultimate) or accept --gitlab-bot-token flag for free-tier instances; store as FULLSEND_FORGE_TOKEN CI/CD variable
  • Detect GitLab tier and create pipeline schedules (dual 5min/15min for enterprise, hourly for free)
  • Set forge-aware required variables (FULLSEND_CREDENTIAL_MODE, FULLSEND_FORGE) instead of GitHub-specific ones (FULLSEND_MINT_URL)

Uninstall (repos uninstall)

  • Clean up pipeline schedules (fullsend-prefixed)
  • Revoke bot project access tokens
  • Remove all fullsend CI/CD variables including poller runtime state (DISPATCHED_KEYS, FAILED_KEYS)
  • Delete .fullsend/config.yaml along with other scaffold files

Also

  • Make checkInstallComponents forge-aware so GitLab repos are not misreported as partially installed
  • Fix glWorkflowRefPattern/glShimRefPattern to not match head_ref:/base_ref: inside dispatch template scripts (word boundary)
  • Add [skip ci] to GitLab scaffold commits to prevent spurious failed pipelines
  • Add --gitlab-bot-token flag and GitLab install docs to repos.md
  • Add forge.gitlab.runner_tags support to repos set-default
  • Add __RUNNER_TAGS__ placeholder to all three GitLab scaffold templates

Files changed (27)

  • internal/cli/repos.go — GitLab post-install/uninstall steps, --gitlab-bot-token flag
  • internal/cli/repos_gitlab.gonew: bot token setup, schedule creation/cleanup, token cleanup
  • internal/cli/repos_gitlab_test.gonew: tests for setup/cleanup functions
  • internal/repos/uninstall.go — GitLab-specific variable list, scaffold paths (including .fullsend/config.yaml), [skip ci]
  • internal/repos/install.go — forge-aware checkInstallComponents, credential mode variables
  • internal/repos/forge_config.go — word boundary fix for ref regex patterns
  • internal/forge/gitlab/ci.goIsEnterprise, CreateProjectAccessToken, ListPipelineSchedules, CreatePipelineSchedule, DeletePipelineSchedule
  • internal/scaffold/installfiles.go — runner tag injection into scaffold templates
  • Full list in diff

Test plan

  • go build ./...
  • go test ./internal/repos/... -count=1
  • go test ./internal/cli/... -run 'TestSetupGitLab|TestCleanupGitLab' -count=1
  • go test ./internal/forge/gitlab/... -count=1
  • go test ./internal/scaffold/... -count=1
  • Manual install/uninstall cycle against gitlab.cee.redhat.com/gallen/integration-service
  • Verified repos status no longer shows $head_ref in REF column
  • Verified uninstall removes poller-state variables
  • Verified [skip ci] produces skipped (not failed) pipeline on scaffold push

Closes #5844

🤖 Generated with Claude Code

Wire GitLab-specific install and uninstall steps into repos management
commands, eliminating the need for separate admin gitlab commands.

Install (repos install):
- Create project access token (Premium/Ultimate) or accept --gitlab-bot-token
  flag for free-tier instances; store as FULLSEND_FORGE_TOKEN CI/CD variable
- Detect GitLab tier and create pipeline schedules (dual 5min/15min for
  enterprise, hourly for free)
- Set forge-aware required variables (FULLSEND_CREDENTIAL_MODE, FULLSEND_FORGE)
  instead of GitHub-specific ones (FULLSEND_MINT_URL)

Uninstall (repos uninstall):
- Clean up pipeline schedules (fullsend-prefixed)
- Revoke bot project access tokens
- Remove all fullsend CI/CD variables including poller runtime state
  (DISPATCHED_KEYS, FAILED_KEYS)

Also:
- Make checkInstallComponents forge-aware so GitLab repos are not
  misreported as partially installed
- Fix glWorkflowRefPattern/glShimRefPattern to not match head_ref:/base_ref:
  inside dispatch template scripts (word boundary)
- Add [skip ci] to GitLab scaffold commits to prevent spurious failed
  pipelines
- Add --gitlab-bot-token flag and GitLab install docs to repos.md
- Add forge.gitlab.runner_tags support to repos set-default

Signed-off-by: Greg Allen <gallen@redhat.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Greg Allen <gallen@redhat.com>
@ggallen
ggallen requested a review from a team as a code owner August 3, 2026 03:17
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 3:17 AM UTC · Completed 3:37 AM UTC
Commit: a56d4bd · View workflow run →

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Unify GitLab install/uninstall under repos (bot token, schedules, runner tags)

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Make repos install/repos uninstall perform GitLab-specific setup/teardown (tokens, schedules,
 variables).
• Add GitLab runner tag defaults and inject tags into GitLab scaffold templates.
• Fix GitLab ref regex matching and improve install status checks for GitLab repos.
Diagram

graph TD
  A["repos install/uninstall CLI"] --> B["Manifest + defaults"] --> C["repos.Install/Uninstall"] --> D["Forge client"] --> E{{"GitLab project APIs"}}
  C --> F["Scaffold generator"]
  A --> G["GitLab setup/cleanup helpers"] --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Move GitLab post-install/uninstall into `internal/repos` layer
  • ➕ Keeps all install/uninstall side effects in one orchestration layer (less CLI coupling)
  • ➕ Easier to ensure dry-run semantics and idempotency consistently
  • ➕ Potentially simpler to reuse for future automation (non-CLI callers)
  • ➖ May require expanding/reshaping interfaces between CLI and repos packages
  • ➖ Could increase scope of this phase and complicate incremental rollout
2. Represent bot token + schedules as declarative desired-state in manifest
  • ➕ Makes behavior explicit and reviewable via config rather than implicit tier detection
  • ➕ Enables deterministic reconcile loops (create/update/delete)
  • ➖ More UX/config surface area (users must understand tier/schedule intent)
  • ➖ Still needs runtime API checks for feature availability (access tokens)
3. Use a group-level token/runner configuration instead of per-project setup
  • ➕ Fewer per-repo resources to create/revoke
  • ➕ Centralized credential management
  • ➖ Broader blast radius if compromised
  • ➖ May not work across mixed permission boundaries; per-project isolation is safer

Recommendation: The PR’s approach (CLI-driven GitLab post-install/uninstall plus forge-aware core install/uninstall) is a good Phase 4 step: it consolidates the operational path under repos without needing separate admin commands. The main follow-up to consider is migrating the GitLab post-install/uninstall helpers into the internal/repos orchestration layer once the behavior stabilizes, to reduce CLI coupling and make future reconciliation/idempotency improvements easier.

Files changed (27) +1165 / -168

Enhancement (15) +613 / -60
repos.goAdd GitLab post-install/post-uninstall orchestration and bot token flag +76/-18

Add GitLab post-install/post-uninstall orchestration and bot token flag

• Introduces '--gitlab-bot-token' and runs GitLab-specific setup after successful installs (store bot token variable, create schedules). Adds GitLab cleanup after successful uninstalls (remove schedules, revoke bot access tokens) and uses '[skip ci]' for GitLab scaffold commits. Removes manifest bootstrapping-on-missing behavior so 'repos install' requires a loadable manifest.

internal/cli/repos.go

repos_gitlab.goImplement GitLab setup/cleanup helpers for bot token and schedules +152/-0

Implement GitLab setup/cleanup helpers for bot token and schedules

• Adds helper functions to create a project access token (or fall back to a provided PAT), store it as 'FULLSEND_FORGE_TOKEN', detect GitLab tier, create polling pipeline schedules, and remove fullsend-prefixed schedules and tokens on uninstall.

internal/cli/repos_gitlab.go

config.goAdd per-repo forge field to config serialization +3/-0

Add per-repo forge field to config serialization

• Adds 'Forge' to per-repo config and marshal structures so configs can explicitly record the forge type.

internal/config/config.go

defaults.goAdd default forge accessor to defaults implementation +3/-0

Add default forge accessor to defaults implementation

• Introduces 'ConfigForge()' default returning empty (auto-detect), aligning with the expanded config interface.

internal/config/defaults.go

interfaces.goExtend PerRepoConfigReader with ConfigForge and inheritance behavior +12/-0

Extend PerRepoConfigReader with ConfigForge and inheritance behavior

• Adds 'ConfigForge()' to the reader interface and implements parent fallback semantics in 'perRepoConfig'.

internal/config/interfaces.go

ci.goAdd GitLab enterprise detection, runner tag discovery, and access token APIs +150/-1

Add GitLab enterprise detection, runner tag discovery, and access token APIs

• Implements '/metadata' enterprise detection, runner tag detection via runners endpoints, and project access token create/list/revoke methods. Updates org plan comments to clarify SaaS vs self-hosted behavior.

internal/forge/gitlab/ci.go

batch_install.goMake install component checks forge-aware and pass GitLab runner tags +2/-1

Make install component checks forge-aware and pass GitLab runner tags

• Updates 'checkInstallComponents' calls to include the resolved forge name. Propagates manifest GitLab runner tags into per-repo install config so scaffold generation can embed them.

internal/repos/batch_install.go

install.goEnable GitLab scaffold install, forge-specific vars, and secrets behavior +80/-24

Enable GitLab scaffold install, forge-specific vars, and secrets behavior

• Removes the previous hard-block on GitLab installs. Makes required variable/secret sets forge-specific, skips GitHub secrets creation for GitLab, generates GitLab scaffold via 'CollectGitLabPerRepoInstallFiles', and writes GitLab runtime state variables used by polling.

internal/repos/install.go

manifest.goAdd GitLab runner_tags field to manifest forge config +2/-1

Add GitLab runner_tags field to manifest forge config

• Extends the GitLab forge section in the manifest to include an optional 'runner_tags' list.

internal/repos/manifest.go

manifest_edit.goSupport forge.gitlab.runner_tags in repos set-default +17/-0

Support forge.gitlab.runner_tags in repos set-default

• Adds 'forge.gitlab.runner_tags' as a valid default key, parses comma-separated values, supports clearing tags, and validates against empty segments.

internal/repos/manifest_edit.go

uninstall.goMake uninstall forge-aware (GitLab vars, scaffold paths, and skip-ci) +69/-9

Make uninstall forge-aware (GitLab vars, scaffold paths, and skip-ci)

• Introduces GitLab-specific variable and scaffold deletion lists (including '.fullsend/config.yaml') and skips secrets deletion for GitLab. Adds '[skip ci]' to GitLab scaffold delete commits and threads forge name through uninstall resolution.

internal/repos/uninstall.go

fullsend-agent.ymlAdd runner tags placeholder to GitLab agent job +1/-0

Add runner tags placeholder to GitLab agent job

• Injects 'tags: __RUNNER_TAGS__' into the agent job to support routing jobs to tagged runners.

internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-agent.yml

fullsend-dispatch.ymlAdd runner tags placeholder and propagate tags to generated no-op pipelines +7/-6

Add runner tags placeholder and propagate tags to generated no-op pipelines

• Adds 'tags: __RUNNER_TAGS__' to dispatch job and ensures dynamically written no-op pipeline YAML includes the same tags placeholder.

internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-dispatch.yml

fullsend-poll.ymlAdd runner tags placeholder to GitLab poll job +1/-0

Add runner tags placeholder to GitLab poll job

• Adds 'tags: __RUNNER_TAGS__' to the scheduled polling job to support tagged runner environments.

internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-poll.yml

installfiles.goAdd GitLab scaffold collection with runner tag injection +38/-0

Add GitLab scaffold collection with runner tag injection

• Implements 'CollectGitLabPerRepoInstallFiles' to walk GitLab scaffold templates, replace '__RUNNER_TAGS__' with a YAML inline list, and exclude embedded '.fullsend/config.yaml' so the installer can generate a forge-aware config.

internal/scaffold/installfiles.go

Bug fix (2) +3 / -2
admin.goEnsure per-repo install dry-run scaffold build explicitly uses GitHub forge +1/-0

Ensure per-repo install dry-run scaffold build explicitly uses GitHub forge

• Passes 'ForgeGitHub' into 'repos.InstallConfig' when building scaffold files for the admin per-repo installer path, avoiding implicit/empty forge behavior.

internal/cli/admin.go

forge_config.goPrevent GitLab ref regex from matching head_ref/base_ref template lines +2/-2

Prevent GitLab ref regex from matching head_ref/base_ref template lines

• Adds word boundaries to GitLab ref regex patterns so they don’t match 'head_ref:'/'base_ref:' occurrences inside scripts/templates.

internal/repos/forge_config.go

Tests (8) +500 / -77
repos_gitlab_test.goAdd unit tests for GitLab setup/cleanup helpers +173/-0

Add unit tests for GitLab setup/cleanup helpers

• Covers token creation success/fallback/error, enterprise vs free schedule creation, schedule cleanup filtering by prefix, and nil-client no-op behavior for token cleanup.

internal/cli/repos_gitlab_test.go

repos_test.goRemove tests for manifest bootstrapping via repos install +0/-74

Remove tests for manifest bootstrapping via repos install

• Deletes test coverage for creating a new manifest when missing, matching the new behavior where missing manifests produce an error.

internal/cli/repos_test.go

ci_accesstoken_test.goAdd tests for GitLab project access token lifecycle APIs +106/-0

Add tests for GitLab project access token lifecycle APIs

• Verifies request payloads and response decoding for create/list/revoke, plus error handling for free-tier/forbidden cases.

internal/forge/gitlab/ci_accesstoken_test.go

methods_test.goAdd tests for enterprise detection and runner tag discovery +98/-0

Add tests for enterprise detection and runner tag discovery

• Adds coverage for '/metadata' enterprise boolean behavior and runner tag detection in untagged, all-tagged, and no-online-runner scenarios.

internal/forge/gitlab/methods_test.go

forge_config_test.goAdd regression tests for head_ref/base_ref false-positive matching +11/-0

Add regression tests for head_ref/base_ref false-positive matching

• Adds cases ensuring 'head_ref:'/'base_ref:' lines in templates do not trigger ref replacement/diff logic.

internal/repos/forge_config_test.go

install_test.goSet explicit forge in base install test config and update signatures +4/-3

Set explicit forge in base install test config and update signatures

• Updates install tests to set 'ForgeGitHub' explicitly and adapts 'checkInstallComponents' calls to the new signature including forge name.

internal/repos/install_test.go

manifest_edit_test.goAdd tests for setting/removing/validating GitLab runner_tags defaults +58/-0

Add tests for setting/removing/validating GitLab runner_tags defaults

• Verifies runner tag persistence in YAML output, removal behavior when set to empty, and rejection of empty tag segments like 'tag1,,tag2'.

internal/repos/manifest_edit_test.go

scaffold_gitlab_test.goAdd tests for runner tags placeholder and substitution in GitLab scaffold +50/-0

Add tests for runner tags placeholder and substitution in GitLab scaffold

• Adds assertions that relevant templates contain the placeholder, and that collection substitutes placeholders correctly for both tagged and untagged configurations. Adds direct tests for tag formatting.

internal/scaffold/scaffold_gitlab_test.go

Documentation (2) +49 / -29
repos.mdDocument GitLab bot token flag and GitLab runner_tags default key +49/-15

Document GitLab bot token flag and GitLab runner_tags default key

• Adds '--gitlab-bot-token' documentation and guidance for free-tier GitLab instances. Updates 'repos set-default' docs to include 'forge.gitlab.runner_tags' plus examples, and removes outdated GitLab install limitations text.

docs/cli/repos.md

repo-management.mdRemove manifest bootstrap instructions for repos install +0/-14

Remove manifest bootstrap instructions for repos install

• Deletes the section describing creating a manifest from scratch via positional args to 'repos install', reflecting the new requirement to load an existing manifest.

docs/guides/getting-started/repo-management.md

@ggallen ggallen closed this Aug 3, 2026
@fullsend-ai-retro

fullsend-ai-retro Bot commented Aug 3, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 3:21 AM UTC · Completed 3:32 AM UTC
Commit: a56d4bd · View workflow run →

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📜 Skill insights (1)

Context used
✅ Compliance rules (platform): 54 rules

Grey Divider


Action required

1. Region variable clobbered empty 🐞 Bug ≡ Correctness
Description
installVarsForForge now always writes FULLSEND_GCP_REGION for GitHub, so when cfg.InferenceRegion is
empty (e.g., secret reuse path) it can overwrite a previously valid region with "". BatchInstall’s
validation only checks whether the variable exists (not whether it’s non-empty), so this can
silently leave repos installed but misconfigured.
Code

internal/repos/install.go[R291-296]

+	case ForgeGitHub:
+		return map[string]string{
+			"FULLSEND_MINT_URL":   mintURL,
+			"FULLSEND_GCP_REGION": cfg.InferenceRegion,
+			forge.PerRepoGuardVar: "true",
+		}, nil
Relevance

●●● Strong

Prevents empty-string clobber; aligns with prior accepted “preserve existing inference config”
behavior.

PR-#215

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The install code now always sets FULLSEND_GCP_REGION from cfg.InferenceRegion (which can be empty),
while BatchInstall only checks for existence of the variable and only errors if it is missing, not
if it’s empty. This combination enables clobbering a previously valid region with an empty string
without triggering validation errors.

internal/repos/install.go[289-296]
internal/repos/batch_install.go[177-190]
internal/repos/batch_install.go[324-331]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`installVarsForForge` always includes `FULLSEND_GCP_REGION` for GitHub, which means a run with `cfg.InferenceRegion == ""` will call `CreateOrUpdateRepoVariable(..., "FULLSEND_GCP_REGION", "")` and wipe an existing region value.

### Issue Context
`BatchInstall` allows `--inference-region` to be omitted when `FULLSEND_GCP_REGION` already exists, but it does not validate that the existing value is non-empty. The new unconditional write can therefore clear the region during a converge/reinstall that reuses existing secrets.

### Fix Focus Areas
- internal/repos/install.go[289-296]
- internal/repos/batch_install.go[177-190]
- internal/repos/batch_install.go[324-331]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. GitLab install skips token check 🐞 Bug ≡ Correctness
Description
checkInstallComponents for GitLab only requires FULLSEND_CREDENTIAL_MODE and FULLSEND_FORGE, so it
can treat a repo as fully installed even when FULLSEND_FORGE_TOKEN is missing. The GitLab poll
scaffold explicitly fails when FULLSEND_FORGE_TOKEN is unset, so repos can be reported/handled as
installed but be non-functional.
Code

internal/repos/install.go[R336-340]

+var gitlabRequiredVariables = []string{"FULLSEND_CREDENTIAL_MODE", "FULLSEND_FORGE"}
+
+func requiredVarsForForge(forgeName string) []string {
+	if forgeName == ForgeGitLab {
+		return gitlabRequiredVariables
Relevance

●●● Strong

Clear correctness bug: install check should require runtime token; likely to be fixed.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The GitLab scaffold requires FULLSEND_FORGE_TOKEN at runtime and fails the poll job when it is
missing. However, checkInstallComponents for GitLab only checks for the two variables listed in
gitlabRequiredVariables, so it can incorrectly conclude installation is complete while the poller
cannot run.

internal/repos/install.go[336-380]
internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-poll.yml[52-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`gitlabRequiredVariables` omits `FULLSEND_FORGE_TOKEN`, so `checkInstallComponents(..., ForgeGitLab, ...)` can return `fullyInstalled=true` even when the bot token variable is absent.

### Issue Context
The GitLab poll job exits with an error if `FULLSEND_FORGE_TOKEN` is not set, so token absence is an install-breaking condition and should be part of the “fully installed” criteria.

### Fix Focus Areas
- internal/repos/install.go[336-380]
- internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-poll.yml[52-61]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. GitLab ops bypass forge.Client 📘 Rule violation ⌂ Architecture
Description
New CLI code directly depends on *gitlab.LiveClient and calls GitLab-specific APIs (e.g.,
CreateProjectAccessToken) instead of routing operations exclusively through the forge.Client
interface. This weakens the abstraction boundary and makes multi-forge support harder to maintain.
Code

internal/cli/repos_gitlab.go[R27-30]

+		expiresAt := time.Now().AddDate(1, 0, 0).Format("2006-01-02")
+		token, err := glClient.CreateProjectAccessToken(ctx, owner, repo, fullsendBotTokenName,
+			[]string{"api"}, gitlabAccessLevelMaintainer, expiresAt)
+		if err != nil {
Relevance

●● Moderate

They’ve accepted reducing forge-specific leakage before, but GitLab CLI refactor scope uncertain.

PR-#2415

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062052 requires forge operations to go through forge.Client rather than using
forge-specific clients in non-forge packages. The new CLI helper functions accept and call
*gitlab.LiveClient directly (e.g., CreateProjectAccessToken, ListProjectAccessTokens,
RevokeProjectAccessToken), demonstrating the abstraction bypass.

Rule 1062052: Route all git forge operations through forge.Client
internal/cli/repos_gitlab.go[19-45]
internal/cli/repos_gitlab.go[124-145]
internal/cli/repos.go[667-699]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Forge-specific operations are being performed in `internal/cli/` by directly calling `*gitlab.LiveClient` methods, bypassing the `forge.Client` abstraction.

## Issue Context
The CLI currently casts `forge.Client` to a concrete GitLab client and invokes GitLab-only methods for token management and tier detection. The compliance requirement is that forge operations should be routed through `forge.Client` so higher layers remain forge-agnostic.

## Fix Focus Areas
- internal/cli/repos_gitlab.go[19-56]
- internal/cli/repos_gitlab.go[58-98]
- internal/cli/repos_gitlab.go[124-151]
- internal/cli/repos.go[679-699]
- internal/forge/forge.go[1-200]
- internal/forge/gitlab/ci.go[695-866]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Guide not in admin/user 📜 Skill insight ⌂ Architecture
Description
The modified guide docs/guides/getting-started/repo-management.md is not located under
docs/guides/admin/ or docs/guides/user/ as required. This breaks the mandated guides directory
structure for audience-specific documentation.
Code

docs/guides/getting-started/repo-management.md[L52-55]

-### Creating a manifest from scratch
-
-If you do not have an existing per-org installation to migrate from,
-`repos install` can bootstrap a new manifest for you. Pass repo names
Relevance

● Weak

Guide taxonomy move requests were rejected in similar cases; repo keeps other guide subtrees.

PR-#5454
PR-#5457
PR-#5502

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1062077 requires every guide under docs/guides/ to live under either admin/ or
user/. The changed file remains in docs/guides/getting-started/, which is neither, so the
structure requirement is not met.

docs/guides/getting-started/repo-management.md[1-20]
Skill: writing-user-docs

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A documentation guide under `docs/guides/` was modified but is located outside the required `admin/` or `user/` subdirectories.

## Issue Context
The file `docs/guides/getting-started/repo-management.md` is explicitly a guide and should live under an audience-specific directory (likely `admin/` since it targets platform administrators).

## Fix Focus Areas
- docs/guides/getting-started/repo-management.md[1-40]
- docs/guides/getting-started/README.md[1-200]
- docs/guides/README.md[1-200]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +27 to +30
expiresAt := time.Now().AddDate(1, 0, 0).Format("2006-01-02")
token, err := glClient.CreateProjectAccessToken(ctx, owner, repo, fullsendBotTokenName,
[]string{"api"}, gitlabAccessLevelMaintainer, expiresAt)
if err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Gitlab ops bypass forge.client 📘 Rule violation ⌂ Architecture

New CLI code directly depends on *gitlab.LiveClient and calls GitLab-specific APIs (e.g.,
CreateProjectAccessToken) instead of routing operations exclusively through the forge.Client
interface. This weakens the abstraction boundary and makes multi-forge support harder to maintain.
Agent Prompt
## Issue description
Forge-specific operations are being performed in `internal/cli/` by directly calling `*gitlab.LiveClient` methods, bypassing the `forge.Client` abstraction.

## Issue Context
The CLI currently casts `forge.Client` to a concrete GitLab client and invokes GitLab-only methods for token management and tier detection. The compliance requirement is that forge operations should be routed through `forge.Client` so higher layers remain forge-agnostic.

## Fix Focus Areas
- internal/cli/repos_gitlab.go[19-56]
- internal/cli/repos_gitlab.go[58-98]
- internal/cli/repos_gitlab.go[124-151]
- internal/cli/repos.go[679-699]
- internal/forge/forge.go[1-200]
- internal/forge/gitlab/ci.go[695-866]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread internal/repos/install.go
Comment on lines +291 to +296
case ForgeGitHub:
return map[string]string{
"FULLSEND_MINT_URL": mintURL,
"FULLSEND_GCP_REGION": cfg.InferenceRegion,
forge.PerRepoGuardVar: "true",
}, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

3. Region variable clobbered empty 🐞 Bug ≡ Correctness

installVarsForForge now always writes FULLSEND_GCP_REGION for GitHub, so when cfg.InferenceRegion is
empty (e.g., secret reuse path) it can overwrite a previously valid region with "". BatchInstall’s
validation only checks whether the variable exists (not whether it’s non-empty), so this can
silently leave repos installed but misconfigured.
Agent Prompt
### Issue description
`installVarsForForge` always includes `FULLSEND_GCP_REGION` for GitHub, which means a run with `cfg.InferenceRegion == ""` will call `CreateOrUpdateRepoVariable(..., "FULLSEND_GCP_REGION", "")` and wipe an existing region value.

### Issue Context
`BatchInstall` allows `--inference-region` to be omitted when `FULLSEND_GCP_REGION` already exists, but it does not validate that the existing value is non-empty. The new unconditional write can therefore clear the region during a converge/reinstall that reuses existing secrets.

### Fix Focus Areas
- internal/repos/install.go[289-296]
- internal/repos/batch_install.go[177-190]
- internal/repos/batch_install.go[324-331]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread internal/repos/install.go
Comment on lines +336 to +340
var gitlabRequiredVariables = []string{"FULLSEND_CREDENTIAL_MODE", "FULLSEND_FORGE"}

func requiredVarsForForge(forgeName string) []string {
if forgeName == ForgeGitLab {
return gitlabRequiredVariables

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

4. Gitlab install skips token check 🐞 Bug ≡ Correctness

checkInstallComponents for GitLab only requires FULLSEND_CREDENTIAL_MODE and FULLSEND_FORGE, so it
can treat a repo as fully installed even when FULLSEND_FORGE_TOKEN is missing. The GitLab poll
scaffold explicitly fails when FULLSEND_FORGE_TOKEN is unset, so repos can be reported/handled as
installed but be non-functional.
Agent Prompt
### Issue description
`gitlabRequiredVariables` omits `FULLSEND_FORGE_TOKEN`, so `checkInstallComponents(..., ForgeGitLab, ...)` can return `fullyInstalled=true` even when the bot token variable is absent.

### Issue Context
The GitLab poll job exits with an error if `FULLSEND_FORGE_TOKEN` is not set, so token absence is an install-breaking condition and should be part of the “fully installed” criteria.

### Fix Focus Areas
- internal/repos/install.go[336-380]
- internal/scaffold/fullsend-repo-gitlab/.gitlab/ci/fullsend-poll.yml[52-61]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #5846feat(repos): make repos install/uninstall the single path for GitLab

Timeline

  1. 01:54 UTCggallen filed issue #5844 (GitLab Phase 4 spec)
  2. 01:55 UTC — Triage agent labeled the issue
  3. 02:08 UTCggallen triggered /fs-code
  4. 02:36 UTC — Code agent opened PR #5845 (agent/5844-gitlab-repos-install-uninstall) — +1165/−168 across 27 files
  5. 02:37–02:53 UTC — Review agent reviewed PR feat(#5844): make repos install/uninstall the single path for GitLab #5845 and submitted CHANGES_REQUESTED with 8 findings (1 critical, 2 high, 3 medium, 2 low)
  6. 02:54 UTC — Fix agent dispatched on PR feat(#5844): make repos install/uninstall the single path for GitLab #5845 but failed eligibility check — misidentified the bot-authored PR as human-authored (known bug #5536)
  7. 03:17 UTCggallen opened PR #5846 (gitlab/cli-phase4) — a separate manual PR for the same feature
  8. 03:17 UTC — Review agent dispatched on PR feat(repos): make repos install/uninstall the single path for GitLab #5846 (run 30781440203)
  9. 03:18 UTCggallen closed PR feat(repos): make repos install/uninstall the single path for GitLab #5846 without merge (~2 minutes after opening, no explanation comment)
  10. 03:19 UTCggallen force-pushed fixes to PR feat(#5844): make repos install/uninstall the single path for GitLab #5845 (manually addressing review findings since the fix agent couldn't run)
  11. 03:19 UTC — Retro dispatched on closed PR feat(repos): make repos install/uninstall the single path for GitLab #5846 (run 30781512588)
  12. 03:22 UTC — Qodo review bot posted 3 findings on the already-closed PR feat(repos): make repos install/uninstall the single path for GitLab #5846

What went well

Review quality on PR #5845 was excellent. The review agent found 8 valid findings with zero apparent false positives across a large, complex 27-file change:

  • Critical: Missing Forge field in ResolvedConfig struct literal (uninstall.go:167) causing GitLab repos to be uninstalled with GitHub variable/secret/path lists — a credential leak where the bot PAT (FULLSEND_FORGE_TOKEN) would survive uninstall
  • High: Stale documentation still claiming repos install only supports GitHub
  • Medium: Missing expires_at on CreateProjectAccessToken, plus idempotency gaps causing duplicate tokens/schedules on re-install
  • Low: Missing GitLab-specific uninstall tests and a silent nil return on type assertion failure

This is strong evidence for the review agent's capability on forge-abstraction and security-sensitive Go code.

Complementary review perspectives: Qodo's review on PR #5846 found different bugs (region variable clobbered empty, install skipping token check, architecture concern about bypassing forge.Client interface). The fullsend review agent and Qodo found non-overlapping issues, suggesting both provide value.

Evidence for existing issues (no new proposals needed)

All improvement areas identified in this retro are already well-tracked:

Conclusion

The code-to-review pipeline worked well: the code agent produced a substantial working implementation, and the review agent caught a critical credential-leak bug. The main gap was the fix agent eligibility bug (#5536) breaking the autonomous fix cycle, which forced human rework. The wasted compute on the immediately-closed PR #5846 (review + retro both dispatched) is a known issue being consolidated under #5817. No new proposals are warranted.

@fullsend-ai-review

Copy link
Copy Markdown

Review skipped — this PR is already closed.

The /fs-review command only reviews open pull requests.

Posted by fullsend post-review check

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.

feat(repos): make repos install/uninstall the single path for GitLab (Phase 4)

1 participant