Skip to content

Add GitLab source and webhook support for TaskSpawners - #1721

Open
sladg wants to merge 7 commits into
kelos-dev:mainfrom
sladg:gitlab-source
Open

sladg wants to merge 7 commits into
kelos-dev:mainfrom
sladg:gitlab-source

Conversation

@sladg

@sladg sladg commented Sep 2, 2026 •

Copy link
Copy Markdown

What type of PR is this?

/kind api
/kind feature

What this PR does / why we need it:

Adds GitLab (gitlab.com and self-managed) as a TaskSpawner source: polling and webhooks for issues and merge requests, CI/review gates, and Task status reported back as notes. Covers the GitLab half of #906.

Workspace provider

  • Workspace.spec.provider (github default, gitlab) selects the secret key, agent credentials, git credential username, and preconfigured CLI. One provider table (internal/controller/workspace_provider.go) drives every workspace-secret read.
  • provider: gitlab requires a non-blank GITLAB_TOKEN key; a missing key fails the Task before Job creation and sets the TaskSpawner Failed. A GitLab source on a GitHub Workspace (or vice versa) fails the TaskSpawner. No fallback to GITHUB_TOKEN. ghproxy with provider: gitlab is rejected by CEL.
  • Agent pods get GITLAB_TOKEN, GITLAB_HOST, a mounted token file, GLAB_CONFIG_DIR, git username oauth2, and no GITHUB_TOKEN/GH_*. GitHub pods are unchanged.

Polling spec.when.gitlab

  • One project (baseUrl, project), types: [issues, mergeRequests], labels, excludeLabels, state, commentPolicy (trigger, excludes, allowed users).
  • MR gates: pipelineStatus (head pipeline, one detail call per MR since the list endpoint omits it) and reviewState (approved from /approvals, changes_requested from detailed_merge_status == requested_changes).
  • Template variables: {{.Kind}}, {{.Branch}}, {{.PipelineStatus}}, {{.PipelineURL}}, {{.ReviewState}}, {{.ReviewComments}} (diff notes as path:line), {{.Comments}} (discussion notes).

Webhooks spec.when.gitlabWebhook

  • issue, merge_request, note, pipeline, push, tag_push with per-event filters. Token checked against X-Gitlab-Token (constant-time); dedup by Idempotency-Key, then X-Gitlab-Event-UUID, then body hash.
  • Served by kelos-webhook-gitlab (--source=gitlab, --gitlab-base-url / Helm webhookServer.sources.gitlab.baseUrl) or a WebhookGateway with spec.gitlab (secretRef, apiBaseURL, credentialsRef). The instance URL for status notes comes only from that config, never from the payload.

Reporting

  • GitLabReporter posts and updates notes; reporting.comments.mode is PerTask or Sticky. The watcher takes a CommentTarget{Kind, Number} so a reporter picks the endpoint.
  • Task annotations for comment and check reporting are now provider-neutral (kelos.dev/comment-*, kelos.dev/check-*); the old kelos.dev/github-* keys are still read so in-flight Tasks keep their comment/check across the upgrade.

Shared code (GitHub and GitLab)

  • When.Tracker() gives a provider-neutral view of the tracker source; CommentsReporting replaces the per-provider reporting types.
  • internal/source: trackerPoller (list/enrich/commentPolicy) drives one discoverTracker pipeline; one restClient with per-provider auth and pagination; one comment policy (comment_policy.go) with per-provider authorizers.
  • internal/webhook: webhookProvider interface with one file per source; the handler has no per-source switches.

Also: --gitlab-* deployment flags, WebhookGateway secret validation, Helm webhookServer.sources.gitlab, kelos get taskspawner output, telemetry kinds gitlab/gitlab_webhook, glab 1.116.0 in all agent images with a token-refreshing wrapper, docs/reference.md, docs/integration.md, examples/19-taskspawner-gitlab.

Design notes

  • One gitlab field with types instead of gitlabIssues/gitlabMergeRequests (Integration: Add GitLab support for Workspace and TaskSpawner source #373).
  • Explicit provider rather than hostname detection; self-managed hosts are not detectable, and Bitbucket needs the same hook.
  • GitLab API surface is v1alpha2-only; v1alpha1 round-trips preserve the fields via kelos.dev/v1alpha2-* annotations.
  • Follow-ups: assignee/author/targetBranch filters, --provider for kelos create workspace/kelos run, pre-clone token check in WorkerPool/Session controllers.

Which issue(s) this PR is related to:

Related to #906 (GitLab portion). Related to #373 and #701 (closed as duplicates of #906).

Special notes for your reviewer:

  • GitLab endpoints, fields, and headers were checked against the GitLab docs. "Request changes" is available on GitLab Free, so reviewState: changes_requested works on every tier.
  • Manually verified on self-managed GitLab 19.2.1 in-cluster: polling with /kelos fix notes, pipelineStatus: failed, reviewState: changes_requested, per-source webhook server end to end (token validation, resend dedup, Sticky notes, own notes filtered), fail-fast on provider mismatch and missing GITLAB_TOKEN, glab from a real Task. WebhookGateway mode for GitLab is covered by unit tests only.
  • For provider: github the generated pods are identical apart from env ordering.
  • helm upgrade --reuse-values fails on the new webhookServer.sources.gitlab block, as it did for linear and generic; use --reset-then-reuse-values.
  • Tests: make test, make test-integration, make verify clean. No e2e, consistent with the Linear and generic sources.

Does this PR introduce a user-facing change?

Add GitLab as a first-class TaskSpawner source. `Workspace.spec.provider` (`github`, default, or `gitlab`) selects the git hosting provider; `provider: gitlab` authenticates with a `GITLAB_TOKEN` secret key, exports `GITLAB_TOKEN`/`GITLAB_HOST` to agent containers, and preconfigures `glab` (now installed in all agent images). `spec.when.gitlab` polls a GitLab project (gitlab.com or self-managed) for issues and merge requests with label, state, comment-command, `pipelineStatus`, and `reviewState` gates, and reports Task status as notes. `spec.when.gitlabWebhook` spawns Tasks in real time from GitLab issue, merge_request, note, pipeline, push, and tag_push webhooks, served by the new `kelos-webhook-gitlab` server (`webhookServer.sources.gitlab` in Helm) or a WebhookGateway with `spec.gitlab`. GitLab fields are available in the v1alpha2 API only. Task annotations for comment and check reporting are renamed to `kelos.dev/comment-*` and `kelos.dev/check-*`; the previous `kelos.dev/github-*` keys are still read. Upgrading with `helm upgrade --reuse-values` requires `--reset-then-reuse-values` because of the new `webhookServer.sources.gitlab` values block.

@CLAassistant

CLAassistant commented Sep 2, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added kind/api Categorizes issue or PR as related to API changes kind/feature Categorizes issue or PR as related to a new feature needs-triage needs-priority needs-actor release-note labels Sep 2, 2026
Add GitLab as a first-class TaskSpawner source: spec.when.gitlab polls a
project for issues and merge requests with label, state, comment-command,
pipelineStatus and reviewState gates and reports Task status as notes;
spec.when.gitlabWebhook spawns Tasks from GitLab webhooks via a new
kelos-webhook-gitlab server or a WebhookGateway with spec.gitlab.

Introduce Workspace.spec.provider (github|gitlab) so the secret key, agent
credentials, git username and preconfigured CLI follow the git host. GitLab
workspaces require a GITLAB_TOKEN key and get glab installed and configured
in all agent images. GitLab fields live in v1alpha2 only and are preserved
across v1alpha1 round-trips.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review completed against the latest diff

Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.

Re-trigger cubic

Comment thread cmd/kelos-spawner/reconciler.go
Comment thread internal/controller/job_builder.go
Comment thread internal/controller/taskspawner_deployment_builder.go Outdated
Comment thread internal/controller/taskspawner_deployment_builder.go
Comment thread cursor/Dockerfile Outdated
Comment thread internal/telemetry/telemetry.go
Comment thread cmd/kelos-webhook-server/reporting_test.go
Comment thread internal/source/gitlab_comment_policy.go

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 86 files (changes from recent commits).

Requires human review: Auto-approval blocked because this review re-detected 15 unresolved issues already reported by Cubic.

Re-trigger cubic

Comment thread internal/source/gitlab.go Outdated
Comment thread internal/controller/webhookgateway_controller.go Outdated
Comment thread examples/19-taskspawner-gitlab/taskspawner-ci-remediation.yaml
Comment thread internal/controller/taskspawner_deployment_builder.go Outdated
Comment thread internal/source/gitlab.go Outdated
Comment thread internal/webhook/signature.go
Comment thread internal/controller/task_controller.go
Comment thread examples/19-taskspawner-gitlab/taskspawner-webhook.yaml
Comment thread internal/cli/printer.go
Comment thread api/v1alpha2/taskspawner_types.go Outdated
@sladg

sladg commented Sep 2, 2026

Copy link
Copy Markdown
Author

TODO for myself - validate webhook based setup for Gitlab, I would like to run the changed code for couple days to verify

…t gitlab (and bitbucket) with unified approach

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

8 issues found across 75 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="internal/source/comment_policy.go">

<violation number="1" location="internal/source/comment_policy.go:48">
P2: When `excludeComments` contains an empty string, every empty body or blank line matches it as an exclude command, so the source can block all items unexpectedly. Ignore empty commands or reject them with API validation before evaluating the policy.</violation>
</file>

<file name="internal/webhook/provider_generic.go">

<violation number="1" location="internal/webhook/provider_generic.go:24">
P2: When multiple generic TaskSpawners share a source, this can deduplicate using the wrong identity. If the first spawner's `id` path is absent but a later spawner's path resolves, retries with equivalent JSON but different formatting use different body hashes and create duplicate Tasks; derive delivery identity per matched spawner or enforce one shared mapping per source.</violation>
</file>

<file name="api/v1alpha2/taskspawner_tracker.go">

<violation number="1" location="api/v1alpha2/taskspawner_tracker.go:15">
P3: For webhook sources, an empty `Repository` or `Project` means unrestricted delivery, not the Workspace repository. Clarify this contract so future callers do not treat an empty webhook restriction as a workspace fallback.</violation>

<violation number="2" location="api/v1alpha2/taskspawner_tracker.go:41">
P1: When a `When` object contains both `jira` and `gitlab`, this switch returns GitLab while the spawner constructs the Jira source, causing provider validation and reporting to target the wrong system. Reject multiple non-nil source fields before selecting a tracker, or add one-of validation to `When`.</violation>
</file>

<file name="internal/webhook/provider_linear.go">

<violation number="1" location="internal/webhook/provider_linear.go:51">
P2: When a Comment delivery reaches an Issue-only spawner with an unscoped label filter, this condition still performs a Linear API lookup even though the spawner cannot match Comment events. Include the parent `LinearWebhook.Types` in the enrichment decision so only spawners that listen for Comment can trigger the fetch.</violation>
</file>

<file name="gemini/Dockerfile">

<violation number="1" location="gemini/Dockerfile:34">
P3: `grep "${DEB}"` treats ${DEB} as a basic regex (dots match any char) and does substring matching, so it can match unintended lines in the checksums file. If more than one line matches, `sha256sum -c -` tries to verify files that are not present in /tmp and the build fails; if none matches, empty stdin makes sha256sum abort. Anchor to the exact filename with a fixed-string match: `grep -Fx "${DEB}" glab-checksums.txt | sha256sum -c -`.</violation>
</file>

<file name="cursor/Dockerfile">

<violation number="1" location="cursor/Dockerfile:34">
P3: `grep "${DEB}"` is an unanchored substring (and regex — dots are unescaped) match. If the GitLab checksums.txt ever lists a sibling asset whose name embeds the DEB string (e.g. a `.deb.asc`/`.deb.sha256` entry), grep matches multiple lines and `sha256sum -c -` then checks every matched file; any matched-but-absent file fails the build where the previous `--ignore-missing -c` variant would have passed. Anchor the match to the end-of-line filename (and treat it as a fixed string) so only the exact downloaded DEB is verified.</violation>
</file>

<file name="internal/controller/taskspawner_deployment_builder.go">

<violation number="1" location="internal/controller/taskspawner_deployment_builder.go:63">
P2: For a `spec.when.gitlabWebhook` spawner, Tracker() reports Provider == GitLab while `ts.Spec.When.GitLab` is nil, so `gitLabSourceArgs(ts.Spec.When.GitLab, ...)` dereferences a nil pointer (gl.BaseURL/gl.Project). The condition was broadened from `ts.Spec.When.GitLab != nil` to the provider check, which no longer guarantees the field is set. Webhook spawners currently route around Build(), so this is latent, but keep the check and the dereference consistent: branch on `ts.Spec.When.GitLab != nil` (or guard the nil) rather than the GitLab provider value.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

// webhook, slack) or when no source is set.
func (w When) Tracker() (TrackerSource, bool) {
switch {
case w.GitHubIssues != 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.

P1: When a When object contains both jira and gitlab, this switch returns GitLab while the spawner constructs the Jira source, causing provider validation and reporting to target the wrong system. Reject multiple non-nil source fields before selecting a tracker, or add one-of validation to When.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api/v1alpha2/taskspawner_tracker.go, line 41:

<comment>When a `When` object contains both `jira` and `gitlab`, this switch returns GitLab while the spawner constructs the Jira source, causing provider validation and reporting to target the wrong system. Reject multiple non-nil source fields before selecting a tracker, or add one-of validation to `When`.</comment>

<file context>
@@ -0,0 +1,90 @@
+// webhook, slack) or when no source is set.
+func (w When) Tracker() (TrackerSource, bool) {
+	switch {
+	case w.GitHubIssues != nil:
+		return githubTracker(w.GitHubIssues.Repo, w.GitHubIssues.PriorityLabels, w.GitHubIssues.Reporting, false), true
+	case w.GitHubPullRequests != nil:
</file context>

}

bodyHasTrigger := cmds.Trigger != "" && containsCommand(body, cmds.Trigger)
bodyHasExclude := len(cmds.Excludes) > 0 && containsAnyCommand(body, cmds.Excludes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When excludeComments contains an empty string, every empty body or blank line matches it as an exclude command, so the source can block all items unexpectedly. Ignore empty commands or reject them with API validation before evaluating the policy.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/source/comment_policy.go, line 48:

<comment>When `excludeComments` contains an empty string, every empty body or blank line matches it as an exclude command, so the source can block all items unexpectedly. Ignore empty commands or reject them with API validation before evaluating the policy.</comment>

<file context>
@@ -0,0 +1,191 @@
+	}
+
+	bodyHasTrigger := cmds.Trigger != "" && containsCommand(body, cmds.Trigger)
+	bodyHasExclude := len(cmds.Excludes) > 0 && containsAnyCommand(body, cmds.Excludes)
+	var bodyMatches bodyMatch
+	if bodyHasTrigger || bodyHasExclude {
</file context>

if err != nil {
return "", "", &httpError{status: http.StatusBadRequest, message: err.Error()}
}
return sourceName, extractGenericDeliveryID(sourceName, body, spawners()), 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.

P2: When multiple generic TaskSpawners share a source, this can deduplicate using the wrong identity. If the first spawner's id path is absent but a later spawner's path resolves, retries with equivalent JSON but different formatting use different body hashes and create duplicate Tasks; derive delivery identity per matched spawner or enforce one shared mapping per source.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/webhook/provider_generic.go, line 24:

<comment>When multiple generic TaskSpawners share a source, this can deduplicate using the wrong identity. If the first spawner's `id` path is absent but a later spawner's path resolves, retries with equivalent JSON but different formatting use different body hashes and create duplicate Tasks; derive delivery identity per matched spawner or enforce one shared mapping per source.</comment>

<file context>
@@ -0,0 +1,77 @@
+	if err != nil {
+		return "", "", &httpError{status: http.StatusBadRequest, message: err.Error()}
+	}
+	return sourceName, extractGenericDeliveryID(sourceName, body, spawners()), nil
+}
+
</file context>

// them by label, because Linear omits labels from Comment payloads.
func (linearProvider) prepare(ctx context.Context, _ *WebhookHandler, log logr.Logger, parsed *ParsedWebhook, spawners []*kelos.TaskSpawner) {
for _, spawner := range spawners {
if spawnerNeedsLinearLabels(spawner, parsed.Linear) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When a Comment delivery reaches an Issue-only spawner with an unscoped label filter, this condition still performs a Linear API lookup even though the spawner cannot match Comment events. Include the parent LinearWebhook.Types in the enrichment decision so only spawners that listen for Comment can trigger the fetch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/webhook/provider_linear.go, line 51:

<comment>When a Comment delivery reaches an Issue-only spawner with an unscoped label filter, this condition still performs a Linear API lookup even though the spawner cannot match Comment events. Include the parent `LinearWebhook.Types` in the enrichment decision so only spawners that listen for Comment can trigger the fetch.</comment>

<file context>
@@ -0,0 +1,69 @@
+// them by label, because Linear omits labels from Comment payloads.
+func (linearProvider) prepare(ctx context.Context, _ *WebhookHandler, log logr.Logger, parsed *ParsedWebhook, spawners []*kelos.TaskSpawner) {
+	for _, spawner := range spawners {
+		if spawnerNeedsLinearLabels(spawner, parsed.Linear) {
+			enrichLinearCommentLabels(ctx, log, parsed.Linear)
+			return
</file context>

// in that case keep the workspace host so GHES API URLs are preserved.
if overrideHost != "" {
host = overrideHost
if tracker, _ := ts.Spec.When.Tracker(); tracker.Provider == kelos.WorkspaceProviderGitLab {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: For a spec.when.gitlabWebhook spawner, Tracker() reports Provider == GitLab while ts.Spec.When.GitLab is nil, so gitLabSourceArgs(ts.Spec.When.GitLab, ...) dereferences a nil pointer (gl.BaseURL/gl.Project). The condition was broadened from ts.Spec.When.GitLab != nil to the provider check, which no longer guarantees the field is set. Webhook spawners currently route around Build(), so this is latent, but keep the check and the dereference consistent: branch on ts.Spec.When.GitLab != nil (or guard the nil) rather than the GitLab provider value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/controller/taskspawner_deployment_builder.go, line 63:

<comment>For a `spec.when.gitlabWebhook` spawner, Tracker() reports Provider == GitLab while `ts.Spec.When.GitLab` is nil, so `gitLabSourceArgs(ts.Spec.When.GitLab, ...)` dereferences a nil pointer (gl.BaseURL/gl.Project). The condition was broadened from `ts.Spec.When.GitLab != nil` to the provider check, which no longer guarantees the field is set. Webhook spawners currently route around Build(), so this is latent, but keep the check and the dereference consistent: branch on `ts.Spec.When.GitLab != nil` (or guard the nil) rather than the GitLab provider value.</comment>

<file context>
@@ -60,8 +60,8 @@ func (b *DeploymentBuilder) buildPodParts(ts *kelos.TaskSpawner, workspace *kelo
 	if workspace != nil {
-		if gl := ts.Spec.When.GitLab; gl != nil {
-			args = append(args, gitLabSourceArgs(gl, workspace.Repo)...)
+		if tracker, _ := ts.Spec.When.Tracker(); tracker.Provider == kelos.WorkspaceProviderGitLab {
+			args = append(args, gitLabSourceArgs(ts.Spec.When.GitLab, workspace.Repo)...)
 		} else {
</file context>

Webhook bool
// Repo scopes the source to a repository other than the Workspace's: the
// GitHub "owner/repo" override or restriction, or the GitLab project path.
// Empty means the Workspace repository.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: For webhook sources, an empty Repository or Project means unrestricted delivery, not the Workspace repository. Clarify this contract so future callers do not treat an empty webhook restriction as a workspace fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api/v1alpha2/taskspawner_tracker.go, line 15:

<comment>For webhook sources, an empty `Repository` or `Project` means unrestricted delivery, not the Workspace repository. Clarify this contract so future callers do not treat an empty webhook restriction as a workspace fallback.</comment>

<file context>
@@ -0,0 +1,90 @@
+	Webhook bool
+	// Repo scopes the source to a repository other than the Workspace's: the
+	// GitHub "owner/repo" override or restriction, or the GitLab project path.
+	// Empty means the Workspace repository.
+	Repo string
+	// PriorityLabels orders discovered items for polling sources that support it.
</file context>

Comment thread gemini/Dockerfile
&& DEB="glab_${GLAB_VERSION}_linux_${ARCH}.deb" \
&& curl -fsSL -o "/tmp/${DEB}" "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/${DEB}" \
&& curl -fsSL -o /tmp/glab-checksums.txt "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/checksums.txt" \
&& (cd /tmp && grep "${DEB}" glab-checksums.txt | sha256sum -c -) \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: grep "${DEB}" treats ${DEB} as a basic regex (dots match any char) and does substring matching, so it can match unintended lines in the checksums file. If more than one line matches, sha256sum -c - tries to verify files that are not present in /tmp and the build fails; if none matches, empty stdin makes sha256sum abort. Anchor to the exact filename with a fixed-string match: grep -Fx "${DEB}" glab-checksums.txt | sha256sum -c -.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At gemini/Dockerfile, line 34:

<comment>`grep "${DEB}"` treats ${DEB} as a basic regex (dots match any char) and does substring matching, so it can match unintended lines in the checksums file. If more than one line matches, `sha256sum -c -` tries to verify files that are not present in /tmp and the build fails; if none matches, empty stdin makes sha256sum abort. Anchor to the exact filename with a fixed-string match: `grep -Fx "${DEB}" glab-checksums.txt | sha256sum -c -`.</comment>

<file context>
@@ -31,7 +31,7 @@ RUN ARCH=$(dpkg --print-architecture) \
     && curl -fsSL -o "/tmp/${DEB}" "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/${DEB}" \
     && curl -fsSL -o /tmp/glab-checksums.txt "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/checksums.txt" \
-    && (cd /tmp && sha256sum --ignore-missing -c glab-checksums.txt) \
+    && (cd /tmp && grep "${DEB}" glab-checksums.txt | sha256sum -c -) \
     && dpkg -i "/tmp/${DEB}" \
     && rm "/tmp/${DEB}" /tmp/glab-checksums.txt
</file context>
Suggested change
&& (cd /tmp && grep "${DEB}" glab-checksums.txt | sha256sum -c -) \
&& (cd /tmp && grep -Fx "${DEB}" glab-checksums.txt | sha256sum -c -) \

Comment thread cursor/Dockerfile
&& DEB="glab_${GLAB_VERSION}_linux_${ARCH}.deb" \
&& curl -fsSL -o "/tmp/${DEB}" "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/${DEB}" \
&& curl -fsSL -o /tmp/glab-checksums.txt "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/checksums.txt" \
&& (cd /tmp && grep "${DEB}" glab-checksums.txt | sha256sum -c -) \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: grep "${DEB}" is an unanchored substring (and regex — dots are unescaped) match. If the GitLab checksums.txt ever lists a sibling asset whose name embeds the DEB string (e.g. a .deb.asc/.deb.sha256 entry), grep matches multiple lines and sha256sum -c - then checks every matched file; any matched-but-absent file fails the build where the previous --ignore-missing -c variant would have passed. Anchor the match to the end-of-line filename (and treat it as a fixed string) so only the exact downloaded DEB is verified.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cursor/Dockerfile, line 34:

<comment>`grep "${DEB}"` is an unanchored substring (and regex — dots are unescaped) match. If the GitLab checksums.txt ever lists a sibling asset whose name embeds the DEB string (e.g. a `.deb.asc`/`.deb.sha256` entry), grep matches multiple lines and `sha256sum -c -` then checks every matched file; any matched-but-absent file fails the build where the previous `--ignore-missing -c` variant would have passed. Anchor the match to the end-of-line filename (and treat it as a fixed string) so only the exact downloaded DEB is verified.</comment>

<file context>
@@ -31,7 +31,7 @@ RUN ARCH=$(dpkg --print-architecture) \
     && curl -fsSL -o "/tmp/${DEB}" "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/${DEB}" \
     && curl -fsSL -o /tmp/glab-checksums.txt "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/checksums.txt" \
-    && (cd /tmp && sha256sum --ignore-missing -c glab-checksums.txt) \
+    && (cd /tmp && grep "${DEB}" glab-checksums.txt | sha256sum -c -) \
     && dpkg -i "/tmp/${DEB}" \
     && rm "/tmp/${DEB}" /tmp/glab-checksums.txt
</file context>

@gjkim42 gjkim42 added priority/important-soon triage-accepted and removed kind/api Categorizes issue or PR as related to API changes labels Sep 3, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="examples/19-taskspawner-gitlab/README.md">

<violation number="1" location="examples/19-taskspawner-gitlab/README.md:113">
P3: The new sentence leaves line 113 at 127 characters, breaking the ~80-char wrapping used throughout the rest of the file and mixing the filter sentence with the next paragraph's continuation. Wrap the added text so 'For an in-cluster GitLab, point the webhook at the' starts on its own line.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

`gitlab-webhook-secret.yaml`, and the **Issues events**, **Comments** and
**Pipeline events** triggers enabled. Filters accept `labels` (all required)
and `excludeLabels` (any rejects) on `issue`, `merge_request` and `note`
events; note filters use the labels of the commented issue or merge request. For an in-cluster GitLab, point the webhook at the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new sentence leaves line 113 at 127 characters, breaking the ~80-char wrapping used throughout the rest of the file and mixing the filter sentence with the next paragraph's continuation. Wrap the added text so 'For an in-cluster GitLab, point the webhook at the' starts on its own line.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At examples/19-taskspawner-gitlab/README.md, line 113:

<comment>The new sentence leaves line 113 at 127 characters, breaking the ~80-char wrapping used throughout the rest of the file and mixing the filter sentence with the next paragraph's continuation. Wrap the added text so 'For an in-cluster GitLab, point the webhook at the' starts on its own line.</comment>

<file context>
@@ -107,8 +107,10 @@ interval. It needs the GitLab webhook server: set
+`gitlab-webhook-secret.yaml`, and the **Issues events**, **Comments** and
+**Pipeline events** triggers enabled. Filters accept `labels` (all required)
+and `excludeLabels` (any rejects) on `issue`, `merge_request` and `note`
+events; note filters use the labels of the commented issue or merge request. For an in-cluster GitLab, point the webhook at the
 `kelos-webhook-gitlab` Service and allow local network requests in the GitLab
 admin settings (Admin → Settings → Network → Outbound requests). To get status
</file context>
Suggested change
events; note filters use the labels of the commented issue or merge request. For an in-cluster GitLab, point the webhook at the
events; note filters use the labels of the commented issue or merge request.
For an in-cluster GitLab, point the webhook at the

@sladg

sladg commented Sep 5, 2026

Copy link
Copy Markdown
Author

@gjkim42 hey! would love to hear your review. is this acceptable/in right direction? anything to change, update, address differently?

I have since tested with Gitlab webhook configuration and all seems in order. Openrouter via Opencode addition is also tested, happy to provide evidence or separate Gitlab deployment so that we can play around and test if needed.

@gjkim42 gjkim42 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is too huge to review.
Can you separate this PR?
maybe gitlab, gitlabwebhook...

and what's the tracksource?

@sladg

sladg commented Sep 12, 2026

Copy link
Copy Markdown
Author

let me clean it up and break apart to be more digestible. i will do more testing to ensure all works nicely, so should be back next weekend hopefully with cleaned version. thanks!

This branch was successfully deployed

No deployments
ok-to-test — 2ba1124f Deployed Sep 3, 2026 by sladg via fork-e2e / e2e-with-environment #1673
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.

3 participants