Skip to content

feat: dynamic display_name for triggered runs (#4259)#4375

Open
Mdev303 wants to merge 8 commits into
hatchet-dev:mainfrom
Mdev303:issue-4259
Open

feat: dynamic display_name for triggered runs (#4259)#4375
Mdev303 wants to merge 8 commits into
hatchet-dev:mainfrom
Mdev303:issue-4259

Conversation

@Mdev303

@Mdev303 Mdev303 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Let callers set a human-readable label on a run at trigger time, so fanned-out child/bulk runs are distinguishable in the dashboard instead of all sharing the generated <readableId>-<timestamp> name.

Engine (pkg/repository):

  • NormalizeDisplayName: trim; empty/whitespace → unset (generated fallback); truncate to 255 runes (UTF-8/rune-safe). Applied at both SDK and REST ingestion.
  • Single-task runs → v1_task.display_name; multi-step DAGs → v1_dag.display_name (DAG step tasks keep generated names). Excluded from all dedup/idempotency keys, so durable/child re-triggers reuse the first name (first-name-wins).

REST: display_name on the trigger endpoint + admin-path normalization.
Proto/OpenAPI: display_name added (trigger.proto #11, workflows.proto #6) + regen.
SDKs (Python, TypeScript, Go, Ruby): display_name on every run surface (run / run_no_wait / aio variants, bulk & run_many per-item, child spawns). Python: carried through _create_workflow_run_request so the value actually reaches gRPC on the real run path.
Docs: new "Run Names" guide + cross-links; CHANGELOG entries (root + per-SDK).

Description

Adds a dynamic display_name to triggered runs. Today, when a parent fans out many child runs in one batch they all render as the identical <readableId>-<timestamp> (same step id, same batch timestamp), forcing you to open each run's input to tell them apart. This lets the caller set a human-readable label at trigger time that the dashboard renders automatically in the runs list, run-detail header, and children list — no frontend change required (the read side already returns displayName).

Design spec: .context/2026-07-07-dynamic-run-display-name-design.md.

Fixes #4259

Usage

Same call for single-task and DAG workflows; the engine routes the name (single → v1_task, DAG → v1_dag). Array run([...], opts) shares one name across the batch — use runMany/run_many for distinct per-item names. Not applied to scheduled/cron runs.

TypeScriptdisplayName on RunOpts:

wf.run(input, { displayName: 'Acme Corp' });                      // single (also runNoWait)
wf.runMany([{ input, opts: { displayName: 'Acme Corp' } }]);      // per-item bulk (also runManyNoWait)
ctx.runChild(child, input, { displayName: 'Acme Corp' });         // child spawn (also spawnChild / bulkRunChildren)

Python — direct display_name kwarg:

wf.run(input, display_name="Acme Corp")                           # also aio_run / *_no_wait
wf.run_many([wf.create_bulk_run_item(input=x, display_name="Acme Corp")])  # per-item bulk
await child.aio_run(input, display_name="Acme Corp")              # child spawn (inside a task)

Design decisions & trade-offs

  • Literal strings only (no templating/CEL). The caller passes the final label; {{ input.company }}-style expressions are a separate, heavier feature deferred for now.
  • Applies to the run, not to steps. Single-task workflow → the label lands on the task (the task is the run); multi-step DAG → it lands on the DAG run and the step tasks keep their generated names. No per-step naming.
  • Pure replace, structural ids untouched. display_name only changes the display label; step_readable_id and the generated-fallback logic are unchanged.
  • Display-only — never enters any dedup/idempotency key (the key correctness decision). On a durable replay or a child re-trigger, the run is matched on its unchanged idempotency key and reused with the first spawn's stored name; a changed display_name is silently ignored and does not raise NonDeterminismError. Editing a cosmetic label must never break a durable run ("first-name-wins").
  • Empty/whitespace → generated fallback; >255 runes → truncated, never rejected. Rune-safe (a multibyte character is never split). No client-side length check — the server truncates, so behavior is identical across languages.
  • No DB migration. Reuses the existing v1_task.display_name / v1_dag.display_name columns.
  • Backend + SDK + REST only — no UI trigger-form field, and no naming of event / cron / scheduled-triggered runs (those paths leave it unset → generated fallback). No new index/search on display_name (metadata filtering already covers search).

Two implementation-time decisions worth calling out:

  • Per-layer option names rather than one global name: sdks/go uses WithRunDisplayName (matching its WithRun* convention) while pkg/client uses WithDisplayName (matching its WithPriority).
  • Python direct kwarg, not just the options object: display_name is a first-class keyword on every run surface (the options= object still works but emits a deprecation warning), and it is threaded through _create_workflow_run_request so it reaches gRPC on the real run/run_many/child-spawn path — an adversarial review caught that an earlier version dropped it there.

Type of change

  • New feature (non-breaking change which adds functionality)

Backward compatible: the field is optional; omitting it reproduces today's exact generated-name behavior.

What's Changed

  • Engine: NormalizeDisplayName + threading through TriggerTaskDatatriggerTuplecreateDAGOpts/CreateTaskOpts; single-task → v1_task, DAG → v1_dag.
  • Proto (trigger.proto Feat: introduce a scheduling state #11, workflows.proto chore(contributing): adapt deps, hardcode generator versions #6) + OpenAPI displayName, generated code regenerated.
  • REST trigger endpoint + admin-path normalization.
  • SDKs — Python, TypeScript, Go, Ruby — display_name/displayName on every run surface (run/run_no_wait/aio, bulk & per-item run_many, child spawns).
  • Docs: new "Run Names" guide + cross-links from Additional Metadata and Child Spawning.
  • CHANGELOG: root + per-SDK (Python/TypeScript/Ruby) entries.

Checklist

Changes have been:

  • Tested — engine unit (NormalizeDisplayName, idempotency-key invariance) + integration (single/DAG/fallback/whitespace/255-rune/run_many/first-name-wins), REST handler, per-SDK request-builder tests, plus live end-to-end smoke tests against an engine built from this branch (Python + TypeScript): named top-level runs, per-item run_many names, a named DAG with generated step names, and whitespace→fallback — all read back through the REST API.

  • Linted and formatted

  • Documented (Run Names guide + cross-links)

  • Added to CHANGELOG (root + per-SDK) — see Keep a Changelog

  • I tested manually the the TS and python SDK but not the others.


🤖 AI Disclosure
  • I acknowledge that an LLM was used in the creation of this Pull Request, in accordance with Hatchet's AI_POLICY.md.

  • Details: Implemented with Claude Code (Claude Opus 4.8) — engine/SDK/REST implementation, unit + integration + per-SDK tests, docs, changelog entries, an adversarial review pass (which caught the Python gRPC-dropping bug), live e2e smoke verification, and drafting this PR. All changes were run and verified locally.

Let callers set a human-readable label on a run at trigger time, so fanned-out
child/bulk runs are distinguishable in the dashboard instead of all sharing the
generated <readableId>-<timestamp> name.

Engine (pkg/repository):
- NormalizeDisplayName: trim; empty/whitespace -> unset (generated fallback);
  truncate to 255 runes (UTF-8/rune-safe). Applied at both SDK and REST ingestion.
- Single-task runs -> v1_task.display_name; multi-step DAGs -> v1_dag.display_name
  (DAG step tasks keep generated names). Excluded from all dedup/idempotency keys,
  so durable/child re-triggers reuse the first name (first-name-wins).

REST: display_name on the trigger endpoint + admin-path normalization.
Proto/OpenAPI: display_name added (trigger.proto hatchet-dev#11, workflows.proto hatchet-dev#6) + regen.
SDKs (Python, TypeScript, Go, Ruby): display_name on every run surface
  (run / run_no_wait / aio variants, bulk & run_many per-item, child spawns).
  Python: carried through _create_workflow_run_request so the value actually
  reaches gRPC on the real run path.
Docs: new "Run Names" guide + cross-links; CHANGELOG entry.

Tests: engine unit (NormalizeDisplayName, idempotency-key invariance) +
integration (single/DAG/fallback/whitespace/255-rune/run_many/first-name-wins),
REST handler, and per-SDK request-builder tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

@Mdev303 is attempting to deploy a commit to the Hatchet Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added documentation Improvements or additions to documentation sdk-ts Related to the Typescript SDK sdk-go Related to the Go SDK sdk-py Related to the Python sdk engine Related to the core Hatchet engine sdk-ruby Related to the Ruby SDK dashboard Related to the Hatchet dashboard labels Jul 8, 2026
Resolve conflicts for hatchet-dev#4375 (dynamic display_name for triggered runs):
- workflow_run.yaml: keep both new fields (displayName + return_only_id).
- openapi.gen.go: regenerated from merged spec (embedded swagger blob + DisplayName type).
- data-contracts.ts: keep both displayName and return_only_id.
- 12 *.pb.go: take main's protoc v5.29.6 header, keep merged body (display_name intact).
- CHANGELOG.md / sdks/python/CHANGELOG.md: keep both Unreleased entries + main's releases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Mdev303 Mdev303 marked this pull request as draft July 8, 2026 01:54
Mdev303 and others added 3 commits July 8, 2026 03:57
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ay_name)

- Run prettier over condition.ts / timestamp.ts, which were regenerated with
  protoc v7.35.1 (double quotes) but missed the prettier:fix pass, unlike the
  other v7.35.1-regenerated protoc files. Fixes prettier:check.
- Document the new `display_name` param in the 9 run/run_no_wait/create_bulk_run_item
  docstrings. Fixes pydoclint DOC101/DOC103.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Revert condition.ts / timestamp.ts to origin/main: their only changes were
  unrelated protoc v3.19.1->v7.35.1 regeneration churn, not part of the
  display_name feature. Reverting keeps the PR to just the files that must
  change (trigger.ts / workflows.ts). Verified tsc + lint:check still pass.
- Prettier-fix the child-spawning.mdx Callout block added by this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Mdev303 Mdev303 marked this pull request as ready for review July 8, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dashboard Related to the Hatchet dashboard documentation Improvements or additions to documentation engine Related to the core Hatchet engine sdk-go Related to the Go SDK sdk-py Related to the Python sdk sdk-ruby Related to the Ruby SDK sdk-ts Related to the Typescript SDK

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] dynamic names for hatchet tasks

1 participant