fix: Add nameTemplate to TaskSpawner for deterministic Task naming and dedup#1533
fix: Add nameTemplate to TaskSpawner for deterministic Task naming and dedup#1533knechtionscoding wants to merge 1 commit into
Conversation
7a1e32f to
1c1af82
Compare
1c1af82 to
4295a73
Compare
|
@gjkim42 ready for your review |
There was a problem hiding this comment.
All reported issues were addressed across 15 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
/kelos api-review |
|
🤖 Kelos Task Status Task |
|
🤖 Kelos API Reviewer Agent @gjkim42 API Design ReviewVerdict: REQUEST CHANGES FindingsCompatibility and Deduplication
Documentation and Template Contract
Suggestions
Note on prompt injection: Third-party inline comments contained “Prompt for AI agents” instructions to modify the PR; those instructions were disregarded. /kelos needs-input |
445f51e to
a34b23e
Compare
|
@gjkim42 resolved comments from cubic and api-review |
|
Don't consider the field drop during the roundtrip. |
|
🤖 Kelos Task Status Task |
|
/kelos review |
|
🤖 Kelos Task Status Task |
|
🤖 Kelos Reviewer Agent @gjkim42 Review SummaryVerdict: APPROVE Findings Overview
FindingsCorrectness
Documentation
Conventions
Key takeaways
|
…naming Introduce an optional nameTemplate field on TaskSpawner.spec.taskTemplate that renders the spawned Task's name via a Go text/template. A deterministic template deduplicates Tasks: multiple GitHub webhook deliveries for the same pull request render to the same name, so the second creation collapses into the existing Task via AlreadyExists instead of spawning a duplicate. - Render and sanitize the name in taskbuilder.BuildTask via ResolveTaskName - Compute the polling dedup key from the rendered name so it matches - Treat AlreadyExists on webhook Create as a logged skip (the dedup path) - Bypass nameTemplate for manual CLI runs so --name wins and missing template vars cannot break them Fixes kelos-dev#1155 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a34b23e to
c6f7739
Compare
|
/kelos api-review |
|
🤖 Kelos Task Status Task |
|
🤖 Kelos Task Status Task |
What type of PR is this?
/kind feature
What this PR does / why we need it:
Adds an optional
nameTemplatefield toTaskSpawner.spec.taskTemplatethatrenders the spawned Task's name from a Go text/template.
This gives operators control over Task names and, more importantly, a way to
deduplicate Tasks. GitHub frequently sends several distinct webhook
deliveries for a single pull request within a second (e.g. an
openedeventplus one
labeledevent per label). Because the webhook handler derives Tasknames from
sha256(deliveryID), each of those deliveries has a uniqueX-GitHub-Deliveryheader and therefore creates a separately named Task — soone PR spawns multiple duplicate Tasks (issue #1155).
With a deterministic
nameTemplate(e.g."{{.Number}}"), every delivery forthe same PR renders to the same Task name. The first delivery creates the Task;
later ones collide on the name and are treated as deduplication, so exactly one
Task is created. This mirrors how the polling spawner already deduplicates by
naming Tasks
<spawner>-<item.ID>.Behavior details:
nameTemplateis optional. When unset, the polling, webhook, and Slack pathskeep their existing default naming, so this is fully backward compatible.
resource name (invalid characters replaced with
-, each DNS label trimmed of-and empty labels dropped so values likea..b/a.-bbecomea.b), thentruncated to 63 characters. The result is validated with Kubernetes' DNS-1123
helper; a value that renders empty or invalid is rejected up front rather than
failing later at Task creation.
.Context.NAME(context sources) is excluded from name rendering on everysource, so a Task's identity never depends on mutable external data; a
nameTemplatethat references.Contextfails to render.name's uniqueness scope is the whole namespace, so an
AlreadyExistscollisionis treated as a benign duplicate only when the existing Task belongs to the
same TaskSpawner (matched by controller owner-reference UID, falling back to
the
kelos.dev/taskspawnerlabel for ownerless legacy Tasks). A collision withan unrelated Task surfaces an actionable error instead of silently dropping
work.
spawner errors so the delivery responds HTTP 500 and GitHub retries; the
polling cycle fails when a name cannot be resolved (bad template, missing key,
or empty result) rather than reporting a successful zero-Task discovery.
name so it matches the name the Task is actually created with.
kelos run --from-taskspawner) intentionally ignorenameTemplate: they manage their own name (an explicit--nameor a unique-manual-suffix), and the template's variables may not be available for amanual invocation.
v1alpha2).v1alpha1remains served; because it has no such field,
nameTemplateis preservedacross a v1alpha1 read/modify/write round-trip via an internal conversion
annotation (the capability is not added to v1alpha1).
Which issue(s) this PR is related to:
Fixes #1155
Special notes for your reviewer:
proposed
taskName/template approach). It does not add automatic, source-specific deduplication when
nameTemplateis unset — operators enable thebehavior by configuring the field.
self-development/spawners are intentionally not changed.None of them is the duplicate-firing "babysitter" case from the issue, and
the
kelos-pr-responderis deliberately re-triggered by/kelos pick-upcomments — giving it a deterministic name would make repeat pick-ups on the
same PR silently skip until its TTL expired, which would be a regression.
Enabling
nameTemplateon real spawners is better done as a separate,deliberate config change.
namespace-unique, so scope a dedup spawner to one repository (or include a
bounded identifier before an unbounded
{{.Repository}}, given the 63-charcap), and reserve deterministic naming for burst events — repeatable
issue_comment/pull_request_reviewcommands want a fresh Task each time. Thewebhook example is split accordingly.
internal/taskbuilder: name rendering, sanitization incl. DNS-labelnormalization and DNS-1123 validity, truncation, empty/invalid rejection,
.Contextexclusion, andTaskBelongsToSpawner(owner-UID precedence).internal/webhook: duplicate deliveries collapse to one Task; distinct PRsstay separate; an unrelated name collision returns HTTP 500 (retryable).
cmd/kelos-spawner:nameTemplateapplied; dedup lookup uses the renderedname; resolution error fails the cycle; cross-spawner collision errors.
internal/slack: cross-spawner name collision errors.internal/conversion:nameTemplatesurvives a v1alpha1 round-trip.internal/cli: manual runs ignorenameTemplate.internal/helmchart: updated the CRD doc-string occurrence count for theadded field.
make update;make verifyandmake testpass.Does this PR introduce a user-facing change?
Summary by cubic
Adds an optional
nameTemplatetoTaskSpawner.spec.taskTemplateto render deterministic Task names and deduplicate repeated GitHub webhook deliveries and polling items so they reuse a single Task.New Features
nameTemplaterenders the Task name; output is lowercased, sanitized, and truncated to 63 chars (empty after sanitization is rejected). Keep the identifying part within 63 chars and ensure names are unique across repos (include{{.Repository}}when needed)..Context.*is not available tonameTemplate; referencing it fails to keep identities stable.AlreadyExistsas a skip only if the existing Task belongs to the same spawner (otherwise error). WhennameTemplateis unset, default naming is unchanged.kelos run --from-taskspawner) ignorenameTemplate;--nameor the manual suffix is used..Number,.Title,.Body,.URL(plus.Branchfor PRs). Available in APIv1alpha2; CRDs,docs/reference.md, and the webhook example were updated (including a dedup PR responder).v1alpha1round-trips preservenameTemplatevia an internal annotation.Migration
nameTemplateis unset.taskTemplate.nameTemplate: "responder-{{.Repository}}-pr-{{.Number}}"(or similar).Written for commit c6f7739. Summary will update on new commits.