Skip to content

Carry the credential owner into scheduled spec_task runs - #2900

Merged
lukemarsden merged 1 commit into
mainfrom
feature/002391-fix-critical-helixos
Jul 30, 2026
Merged

Carry the credential owner into scheduled spec_task runs#2900
lukemarsden merged 1 commit into
mainfrom
feature/002391-fix-critical-helixos

Conversation

@binocarlos

Copy link
Copy Markdown
Collaborator

Follow-up to #2899, which routed Claude credentials per owner but only for tasks
created through an orchestrator's dispatch call. That PR named the hole it left:

Known gap: scheduled runs are not routed. HelixOS scheduled bot runs are
Helix cron triggers: they fire inside Helix and create the task directly, so the
field is never set […] This is a genuine hole, not a rounding error: it is the
majority of runs.

This closes it.

The change

CronTrigger gains CredentialOwnerID, and executeSpecTaskAction passes it to
CreateTaskRequest exactly as a hand-dispatched run does. Everything downstream
already works — the same delegation gate added in #2899 still decides whether the
grant is real, so a trigger naming someone who has not delegated their
subscription to the org falls back rather than borrowing their quota.

Credential resolution only. The task is still created by, owned by, and
attributed to the trigger's app owner; nothing runs as the credential owner.
Empty — which is every trigger that exists today — keeps current behaviour
exactly, so this is inert until something writes the field.

Who sets it

Whatever writes the trigger. For HelixOS that is its reconciler, which knows each
bot's owner and writes it at reconcile time (paired PR in helixml/helixos).
Deriving it at fire time is not an option even in principle: Helix has no concept
of a HelixOS bot, so there is nothing on this side to derive an owner from. The
trigger row is the only thing that crosses the boundary.

Nothing in non-test server code constructs a types.CronTrigger{}, and the
trigger UI edits app-config triggers rather than the TriggerConfiguration rows
an orchestrator writes, so there is no path that would silently drop the field
back out on a round trip.

Testing

go build ./api/..., go vet, and go test ./pkg/trigger/cron/... all pass.

  • New TestExecuteCronTask_SpecTaskActionCarriesCredentialOwner: the owner named
    on the trigger reaches CreateTaskRequest, while UserID stays the app owner —
    pinning that this is credential routing and not an ownership change
  • The existing spec_task test now also asserts that a trigger naming nobody does
    not invent an owner

Not tested end to end. No Helix stack was available, so no scheduled trigger
was fired against a live desktop to watch it pick up the named owner's
subscription. The link this PR adds is unit-tested; the resolution it hands off to
was covered by #2899.

A scheduled spec_task authenticated as the app owner, so a fleet of
triggers written under one service key all ran on that account's Claude
subscription. CronTrigger gains CredentialOwnerID, passed to
CreateTaskRequest exactly as a hand-dispatched task does; the existing
delegation check still gates whether the grant applies.

Spec-Ref: helix-specs@5d2aa66:002391_fix-these-critical-bugs
@lukemarsden
lukemarsden merged commit 906bb8b into main Jul 30, 2026
5 checks passed
@lukemarsden
lukemarsden deleted the feature/002391-fix-critical-helixos branch July 30, 2026 10:32
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.

2 participants