Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
eece8ea
Automatic request header support
cretz Jan 29, 2026
3004fce
Populate resource_id fields for request header routing
tconley1428 Mar 11, 2026
9d199d8
Add resource_id population for Nexus task requests
tconley1428 Mar 11, 2026
89129e9
Add comprehensive activity task resource ID tests
tconley1428 Mar 12, 2026
2dfe1a4
Fix partial validation of RecordActivityTaskHeartbeatByIdRequest
tconley1428 Mar 12, 2026
636af76
Remove resource_id implementations for messages without proto fields
tconley1428 Mar 12, 2026
746f006
Complete systematic verification of all resource ID field tests
tconley1428 Mar 12, 2026
a146692
Rename test file and remove working state documentation
tconley1428 Mar 12, 2026
613448b
Update resource ids with prefixes
tconley1428 Mar 18, 2026
c79cc72
Standardize resource_id prefix responsibility in SDK
tconley1428 Mar 20, 2026
c213166
Update go.temporal.io/api to v1.62.6, remove local replace directive
tconley1428 Mar 25, 2026
16e0b56
Merge branch 'master' into populate-resource-id-fields
tconley1428 Mar 25, 2026
f8bdc0f
Remove unused test helper and add empty resource ID test
tconley1428 Mar 25, 2026
26f71c0
Run go mod tidy in internal/cmd/build
tconley1428 Mar 25, 2026
7a7413d
Run go mod tidy across all modules
tconley1428 Mar 25, 2026
68f7164
Merge remote-tracking branch 'origin/master' into populate-resource-i…
tconley1428 Mar 25, 2026
3b10ab8
Merge branch 'master' into populate-resource-id-fields
tconley1428 Mar 26, 2026
7df41ba
Address review feedback: fix CompleteActivity resource ID and nil-safety
tconley1428 Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion contrib/datadog/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ require (
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.temporal.io/api v1.62.5 // indirect
go.temporal.io/api v1.62.6 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions contrib/datadog/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,8 @@ go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.0.1 h1:T
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.0.1/go.mod h1:riqUmAOJFDFuIAzZu/3V6cOrTyfWzpgNJnG5UwrapCk=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.0.1 h1:z/oMlrCv3Kopwh/dtdRagJy+qsRRPA86/Ux3g7+zFXM=
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.0.1/go.mod h1:C7EHYSIiaALi9RnNORCVaPCQDuJgJEn/XxkctaTez1E=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
Expand Down
2 changes: 1 addition & 1 deletion contrib/envconfig/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
go.temporal.io/api v1.62.5 // indirect
go.temporal.io/api v1.62.6 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions contrib/envconfig/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
2 changes: 1 addition & 1 deletion contrib/opentelemetry/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ require (
github.com/stretchr/objx v0.5.2 // indirect
go.opentelemetry.io/otel/metric v1.40.0
go.opentelemetry.io/otel/sdk/metric v1.40.0
go.temporal.io/api v1.62.5 // indirect
go.temporal.io/api v1.62.6 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.24.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions contrib/opentelemetry/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4A
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
2 changes: 1 addition & 1 deletion contrib/opentracing/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/robfig/cron v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
go.temporal.io/api v1.62.5 // indirect
go.temporal.io/api v1.62.6 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions contrib/opentracing/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
2 changes: 1 addition & 1 deletion contrib/sysinfo/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ require (
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.temporal.io/api v1.62.5 // indirect
go.temporal.io/api v1.62.6 // indirect
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions contrib/sysinfo/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
2 changes: 1 addition & 1 deletion contrib/tally/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/robfig/cron v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/twmb/murmur3 v1.1.5 // indirect
go.temporal.io/api v1.62.5 // indirect
go.temporal.io/api v1.62.6 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.13.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions contrib/tally/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ github.com/uber-go/tally/v4 v4.1.1/go.mod h1:aXeSTDMl4tNosyf6rdU8jlgScHyjEGGtfJ/
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/nexus-rpc/sdk-go v0.6.0
github.com/robfig/cron v1.2.0
github.com/stretchr/testify v1.10.0
go.temporal.io/api v1.62.5
go.temporal.io/api v1.62.6
golang.org/x/sync v0.13.0
golang.org/x/sys v0.32.0
golang.org/x/time v0.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/build/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/robfig/cron v1.2.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect
go.temporal.io/api v1.62.5 // indirect
go.temporal.io/api v1.62.6 // indirect
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.39.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/build/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiy
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.temporal.io/api v1.62.5 h1:9R/9CeyM7xqHSlsNt+QIvapQLcRxCZ38bnXQx4mCN6I=
go.temporal.io/api v1.62.5/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
go.temporal.io/api v1.62.6 h1:JLH8y9URdY9WbdvwMXfGknlhohoPBrXOc9AbQkPInOc=
go.temporal.io/api v1.62.6/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
Expand Down
20 changes: 13 additions & 7 deletions internal/grpc_dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
"go.temporal.io/api/proxy"
"go.temporal.io/api/serviceerror"
"go.temporal.io/sdk/internal/common/metrics"
"go.temporal.io/sdk/internal/common/retry"
Expand All @@ -17,6 +18,7 @@ import (
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)

type (
Expand Down Expand Up @@ -157,17 +159,21 @@ func requiredInterceptors(
interceptors = append(interceptors, interceptor)
}
}
// Add namespace provider interceptor
interceptors = append(interceptors, namespaceProviderInterceptor())
// Add temporal header interceptor (namespace + resource ID)
interceptors = append(interceptors, temporalHeaderInterceptor())
return interceptors
}

func namespaceProviderInterceptor() grpc.UnaryClientInterceptor {
func temporalHeaderInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
if nsReq, ok := req.(interface{ GetNamespace() string }); ok {
// Only add namespace if it doesn't already exist
if md, _ := metadata.FromOutgoingContext(ctx); len(md.Get(temporalNamespaceHeaderKey)) == 0 {
ctx = metadata.AppendToOutgoingContext(ctx, temporalNamespaceHeaderKey, nsReq.GetNamespace())
var extractOpts proxy.ExtractHeadersOptions
extractOpts.Request, _ = req.(proto.Message)
extractOpts.ExistingMetadata, _ = metadata.FromOutgoingContext(ctx)
if extractOpts.Request != nil {
if headers, err := proxy.ExtractTemporalRequestHeaders(ctx, extractOpts); err != nil {
return err
} else if len(headers) > 0 {
ctx = metadata.AppendToOutgoingContext(ctx, headers...)
}
}
return invoker(ctx, method, req, reply, cc, opts...)
Expand Down
10 changes: 8 additions & 2 deletions internal/grpc_dialer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ func TestExistingContextMetadataJoinedWithSDKHeaders(t *testing.T) {
)
}

func TestNamespaceInterceptor(t *testing.T) {
func TestTemporalHeaderInterceptor(t *testing.T) {
srv, err := startTestGRPCServer()
require.NoError(t, err)
defer srv.Stop()
Expand All @@ -566,12 +566,18 @@ func TestNamespaceInterceptor(t *testing.T) {
metadata.ValueFromIncomingContext(srv.getSystemInfoRequestContext, temporalNamespaceHeaderKey),
)
// Verify namespace header is set on a request that does have namespace on the request
require.NoError(t, client.SignalWorkflow(context.Background(), "workflowid", "runid", "signalname", nil))
require.NoError(t, client.SignalWorkflow(context.Background(), "test-workflow-id", "runid", "signalname", nil))
require.Equal(
t,
[]string{"test-namespace"},
metadata.ValueFromIncomingContext(srv.lastSignalWorkflowExecutionContext, temporalNamespaceHeaderKey),
)
// Verify resource-id header is also set
require.Equal(
t,
[]string{"workflow:test-workflow-id"},
metadata.ValueFromIncomingContext(srv.lastSignalWorkflowExecutionContext, "temporal-resource-id"),
)
}

func TestCredentialsMTLS(t *testing.T) {
Expand Down
28 changes: 21 additions & 7 deletions internal/internal_task_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1961,6 +1961,7 @@ func (wth *workflowTaskHandlerImpl) completeWorkflow(
BinaryChecksum: wth.workerBuildID,
QueryResults: queryResults,
Namespace: wth.namespace,
ResourceId: fmt.Sprintf("workflow:%s", task.WorkflowExecution.WorkflowId),
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
MeteringMetadata: &commonpb.MeteringMetadata{NonfirstLocalActivityExecutionAttempts: nonfirstLAAttempts},
SdkMetadata: &sdk.WorkflowTaskCompletedMetadata{
LangUsedFlags: langUsedFlags,
Expand Down Expand Up @@ -2300,7 +2301,8 @@ func (ath *activityTaskHandlerImpl) Execute(taskQueue string, t *workflowservice
metricsHandler.Counter(metrics.UnregisteredActivityInvocationCounter).Inc(1)
return convertActivityResultToRespondRequest(ath.identity, t.TaskToken, nil,
NewActivityNotRegisteredError(activityType, ath.getRegisteredActivityNames()),
ath.dataConverter, ath.failureConverter, ath.namespace, false, ath.versionStamp, ath.deployment, ath.workerDeploymentOptions), nil
ath.dataConverter, ath.failureConverter, ath.namespace, false, ath.versionStamp, ath.deployment, ath.workerDeploymentOptions,
t.WorkflowExecution.GetWorkflowId(), t.ActivityId), nil
}

// panic handler
Expand All @@ -2318,7 +2320,8 @@ func (ath *activityTaskHandlerImpl) Execute(taskQueue string, t *workflowservice
metricsHandler.Counter(metrics.ActivityTaskErrorCounter).Inc(1)
panicErr := newPanicError(p, st)
result = convertActivityResultToRespondRequest(ath.identity, t.TaskToken, nil, panicErr,
ath.dataConverter, ath.failureConverter, ath.namespace, false, ath.versionStamp, ath.deployment, ath.workerDeploymentOptions)
ath.dataConverter, ath.failureConverter, ath.namespace, false, ath.versionStamp, ath.deployment, ath.workerDeploymentOptions,
t.WorkflowExecution.GetWorkflowId(), t.ActivityId)
}
}()

Expand Down Expand Up @@ -2365,7 +2368,8 @@ func (ath *activityTaskHandlerImpl) Execute(taskQueue string, t *workflowservice
)
}
return convertActivityResultToRespondRequest(ath.identity, t.TaskToken, output, err,
ath.dataConverter, ath.failureConverter, ath.namespace, isActivityCanceled, ath.versionStamp, ath.deployment, ath.workerDeploymentOptions), nil
ath.dataConverter, ath.failureConverter, ath.namespace, isActivityCanceled, ath.versionStamp, ath.deployment, ath.workerDeploymentOptions,
t.WorkflowExecution.GetWorkflowId(), t.ActivityId), nil
}

func (ath *activityTaskHandlerImpl) getActivity(name string) activity {
Expand Down Expand Up @@ -2425,15 +2429,24 @@ func createNewCommandWithMetadata(commandType enumspb.CommandType, metadata *sdk
}
}

func getActivityResourceIdFromCtx(ctx context.Context) string {
env := getActivityEnvironmentFromCtx(ctx)
if env == nil {
return ""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want test coverage for when env == nil?

}
return getActivityResourceId(env.workflowExecution.ID, env.activityID)
}

func recordActivityHeartbeat(ctx context.Context, service workflowservice.WorkflowServiceClient, metricsHandler metrics.Handler,
identity string, taskToken []byte, details *commonpb.Payloads,
) error {
namespace := getNamespaceFromActivityCtx(ctx)
request := &workflowservice.RecordActivityTaskHeartbeatRequest{
TaskToken: taskToken,
Details: details,
Identity: identity,
Namespace: namespace,
TaskToken: taskToken,
Details: details,
Identity: identity,
Namespace: namespace,
ResourceId: getActivityResourceIdFromCtx(ctx),
}
Comment on lines 2461 to 2471
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 RecordActivityHeartbeatWithOptions in internal/internal_workflow_client.go never passes opts.WorkflowID to recordActivityHeartbeat, so RecordActivityTaskHeartbeatRequest.ResourceId is always empty when heartbeating from outside an activity — the exact primary use case for this API. This means proxy-based resource-id routing introduced by this PR will silently fail for all external/async heartbeat calls even when a WorkflowID is provided. The fix is to thread opts.WorkflowID into recordActivityHeartbeat (adding a workflowID parameter) and fall back to getActivityResourceId(workflowID, "") when the context has no activity environment, mirroring the pattern already used in recordActivityHeartbeatByID.

Extended reasoning...

Bug: RecordActivityHeartbeatWithOptions always produces empty ResourceId

What the bug is and how it manifests

RecordActivityHeartbeatWithOptions is the public API for workers (or application code) that hold an activity task token and want to report heartbeats outside the normal in-activity context — for example, in async or forked-process activity patterns. The caller provides opts.WorkflowID precisely so the system can associate the heartbeat with the correct workflow. PR #2226 adds ResourceId population to RecordActivityTaskHeartbeatRequest for proxy routing, but the RecordActivityHeartbeatWithOptions code path never sets ResourceId to anything other than empty string.

The specific code path that triggers it

RecordActivityHeartbeatWithOptions calls recordActivityHeartbeat(ctx, svc, metrics, identity, opts.TaskToken, data)opts.WorkflowID is referenced only for ActivitySerializationContext and is not forwarded. Inside recordActivityHeartbeat, the request is built with ResourceId: getActivityResourceIdFromCtx(ctx). getActivityResourceIdFromCtx calls getActivityEnvironmentFromCtx(ctx), which returns nil for any context that did not go through the normal in-activity dispatch path (i.e., every external/async caller). When the env is nil, the helper immediately returns "", so ResourceId is always empty for this entire code path.

Why existing code does not prevent it

The same PR correctly fixes recordActivityHeartbeatByID by passing workflowID and activityID as explicit parameters and computing ResourceId: getActivityResourceId(workflowID, activityID). It also fixes CompleteActivityWithOptions to pass opts.WorkflowID. The heartbeat-with-options path was simply missed. There is no guard or fallback that would use opts.WorkflowID as a substitute when the context lookup returns nil.

Impact

Any deployment relying on the temporal-resource-id gRPC header (set by temporalHeaderInterceptor from ResourceId) for proxy routing will receive no header on RecordActivityTaskHeartbeat requests originating from RecordActivityHeartbeatWithOptions. Since this is the primary API for async activities, proxy routing silently fails for a significant class of requests. The omission is invisible at the Go API level — callers supply opts.WorkflowID and have no way to know it is being ignored.

How to fix it

Add a workflowID string parameter to recordActivityHeartbeat and compute ResourceId with a two-stage fallback: first try getActivityResourceIdFromCtx(ctx), then fall back to getActivityResourceId(workflowID, "") if empty. Then update RecordActivityHeartbeatWithOptions to pass opts.WorkflowID and all other internal callers to pass "". This mirrors the pattern already used in recordActivityHeartbeatByID.

Step-by-step proof

  1. A worker receives an activity task token, spawns a goroutine, and calls client.RecordActivityHeartbeatWithOptions(ctx, opts) with opts.WorkflowID = "my-workflow-123" and opts.TaskToken = token.
  2. RecordActivityHeartbeatWithOptions invokes recordActivityHeartbeat(ctx, svc, metrics, identity, opts.TaskToken, data)opts.WorkflowID is not forwarded.
  3. recordActivityHeartbeat builds the request with ResourceId: getActivityResourceIdFromCtx(ctx).
  4. getActivityResourceIdFromCtx calls getActivityEnvironmentFromCtx(ctx) which returns nil (external ctx has no activity env), so it returns "".
  5. The outgoing RecordActivityTaskHeartbeatRequest has ResourceId = "".
  6. temporalHeaderInterceptor calls proxy.ExtractTemporalRequestHeaders, finds no resource id, and sets no temporal-resource-id header.
  7. The proxy cannot route the request to the correct cell, defeating the purpose of this PR for async heartbeats.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

sounds legit


var heartbeatResponse *workflowservice.RecordActivityTaskHeartbeatResponse
Expand Down Expand Up @@ -2465,6 +2478,7 @@ func recordActivityHeartbeatByID(ctx context.Context, service workflowservice.Wo
ActivityId: activityID,
Details: details,
Identity: identity,
ResourceId: getActivityResourceId(workflowID, activityID),
}

var heartbeatResponse *workflowservice.RecordActivityTaskHeartbeatByIdResponse
Expand Down
Loading
Loading