Skip to content

Conversation

@Tamoghnasen9
Copy link
Contributor

What was changed

Support setting of Temporal headers in workflows

Why?

The cli doesn't support the setting of workflow headers. With this change we would be able to pass the headers to Workflows using cli.

Checklist

  1. Closes - [Feature Request] Allow setting Temporal headers when starting workflow #876

  2. How was this tested:

  • Brought up the dev server using : temporal server start-dev
  • Build the cli locally and passed the --headers flag to start a workflow. Checked in Temporal UI, the Header section shows the headers set in Workflow.
  1. Any docs updates needed?
    https://docs.temporal.io/cli/workflow

@Tamoghnasen9 Tamoghnasen9 requested review from a team as code owners December 8, 2025 13:05
@CLAassistant
Copy link

CLAassistant commented Dec 8, 2025

CLA assistant check
All committers have signed the CLA.

May be passed multiple times to set multiple headers.
Can also be made available via environment variable as
`TEMPORAL_GRPC_META_[name]`.
- name: headers
Copy link
Member

Choose a reason for hiding this comment

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

I think this setting should only be on commands where it makes sense, namely workflow start, execute, query, signal, and update (and any signal-with-start or update-with-start).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. Limited it to:

  • workflow execute
  • workflow execute-update-with-start
  • workflow signal
  • workflow signal-with-start
  • workflow start
  • workflow start-update-with-start
  • workflow query
  • workflow update start
  • workflow update execute

Comment on lines 468 to 477
newOpts := cctx.StoredClientOptions
cliHeaderPropagator, err := cliHeaderPropagator(updateStartOpts.Headers)
if err != nil {
return err
}
newOpts.ContextPropagators = append(newOpts.ContextPropagators, cliHeaderPropagator)
execClient, err = client.NewClientFromExisting(cl, newOpts)
if err != nil {
return fmt.Errorf("unable to create header-propagator client: %w", err)
}
Copy link
Member

@cretz cretz Dec 9, 2025

Choose a reason for hiding this comment

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

I think instead of this approach where we store options and make a new client, I think we should have an always-set "header context propagator" that extracts the headers from a known key in the context, and just call something like cl.UpdateWorkflow(ContextWithHeaders(updateStartOpts.Headers), ... where ContextWithHeaders creates a context with that key if there are any headers.

Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

Another thing I noticed while reviewing concerning how payload data is represented. I will also see about getting a CLI team member here to review/approve.


type headerPropagator struct{}

const cliHeaderContextKey = "worflow-headers"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
const cliHeaderContextKey = "worflow-headers"
const cliHeaderContextKey = "workflow-headers"

Or better:

Suggested change
const cliHeaderContextKey = "worflow-headers"
type cliHeaderContextKey struct{}

And use the empty struct when accessing

val := ctx.Value(cliHeaderContextKey)
if headers, ok := val.(map[string]string); ok {
for k, v := range headers {
writer.Set(k, payload.EncodeString(v))
Copy link
Member

Choose a reason for hiding this comment

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

Hrmm, in addition to not wanting to use server utilities in here, I think this may need to be raw data. A user should be able to provide foo="var" or foo=1 and not have the latter become foo="1". They should be able to provide foo={"bar": "baz"}. This is the same approach that is taken everywhere else that accepts payloads, like for --search-attribute and --memo, can we match those (i.e. use stringKeysJSONValues)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, implemented this.

Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

Looks great! Will let CLI team review.

@spkane31
Copy link
Contributor

Can you add a test for this change? You should be able to do something like this (heavy pseudocode):

res := s.Execute("workflow", "start", ..., "--headers", "someHeader=123",...)

s.Client.GetWorkflowHistory(...)

startedEvent := <first enums.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED eventType>

headers := startedEvent.GetWorkflowExecutionStartedEventAttributes().GetHeaders()

require.Equal(t, headers.Get("someHeader"), "123")

that's not perfect by any means but that's the gist of what a test for this should look like.

@Tamoghnasen9
Copy link
Contributor Author

Can you add a test for this change? You should be able to do something like this (heavy pseudocode):

res := s.Execute("workflow", "start", ..., "--headers", "someHeader=123",...)

s.Client.GetWorkflowHistory(...)

startedEvent := <first enums.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED eventType>

headers := startedEvent.GetWorkflowExecutionStartedEventAttributes().GetHeaders()

require.Equal(t, headers.Get("someHeader"), "123")

that's not perfect by any means but that's the gist of what a test for this should look like.

Done @spkane31

@spkane31 spkane31 merged commit fbfc069 into temporalio:main Dec 11, 2025
8 checks passed
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.

4 participants