Skip to content

[Bug] Workflow trigger uniqueness indexes are not team-scoped #164

Description

@michaelmcnees

Summary

The unique indexes on workflow_triggers are global, not team-scoped, so in a multi-tenant deployment two teams cannot register the same webhook path or the same workflow-name + cron schedule — the second team's mantle apply fails with a unique-constraint violation.

From migration 003_triggers.sql:

CREATE UNIQUE INDEX idx_workflow_triggers_webhook_path ON workflow_triggers (path) WHERE type = 'webhook' AND enabled = true;
CREATE UNIQUE INDEX idx_workflow_triggers_cron ON workflow_triggers (workflow_name, schedule) WHERE type = 'cron';

Neither includes team_id, even though workflow_definitions is team-scoped (UNIQUE(team_id, name, version)) and the trigger read paths carry team_id.

Context

Surfaced during review of #162 (Codex P2). Trigger registration was dead before #162, so these inserts never ran and the latent collision was unreachable; #162 makes it reachable. Single-tenant deployments (the default team) are unaffected, which is why it wasn't blocking for #162.

The wrinkle for webhooks

The cron case is a straightforward fix (add team_id to the unique index). Webhooks are not: the inbound POST /hooks/<path> handler resolves team via auth.TeamIDFromContext(r.Context()), which is always the default team for unauthenticated webhooks (see internal/server/webhook.go + LookupWebhookTrigger). So webhook routing isn't team-aware today — making the path team-unique at the DB level would let two teams register the same path, but the handler couldn't disambiguate which team's workflow to run. A correct fix needs a routing decision (e.g. team-prefixed webhook paths, or a per-team webhook token) alongside the index change.

Proposed work

  • Cron/email: migration to make the uniqueness team-scoped (e.g. (team_id, workflow_name, schedule) for cron).
  • Webhook: decide the multi-tenant routing model (team-prefixed path or per-team token), then scope the path index accordingly.
  • Update internal/workflow trigger reconciliation and the server read/handler paths to match.
  • Tests covering two teams registering the same path / same workflow-name+schedule.

Files

  • internal/db/migrations/003_triggers.sql (+ a new migration to alter the indexes)
  • internal/workflow/triggers.go, internal/server/webhook.go, internal/server/trigger.go

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions