diff --git a/Directory.Packages.props b/Directory.Packages.props
index 38177b2..e852a05 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -24,6 +24,7 @@
+
diff --git a/README.md b/README.md
index a09c161..fb9d886 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ Prerequisites:
* [EagerWorkflowStart](src/EagerWorkflowStart) - Demonstrates usage of Eager Workflow Start to reduce latency for workflows that start with a local activity.
* [Encryption](src/Encryption) - End-to-end encryption with Temporal payload codecs.
* [EnvConfig](src/EnvConfig) - Load client configuration from TOML files with programmatic overrides
+* [Gcp/CloudRun/Id](src/Gcp/CloudRun/Id) - Run a Temporal Worker on a Google Cloud Run worker pool, deriving the client identity from Cloud Run metadata.
* [LambdaWorker](src/LambdaWorker) - Run a Temporal Worker inside an AWS Lambda function.
* [Mutex](src/Mutex) - How to implement a mutex as a workflow. Demonstrates how to avoid race conditions or parallel mutually exclusive operations on the same resource.
* [NexusCancellation](src/NexusCancellation) - Demonstrates how to cancel a running Nexus operation from a caller workflow.
diff --git a/TemporalioSamples.sln b/TemporalioSamples.sln
index c945182..90ca1d2 100644
--- a/TemporalioSamples.sln
+++ b/TemporalioSamples.sln
@@ -145,6 +145,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NexusStandaloneActivity", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusStandaloneActivity", "src\NexusStandaloneActivity\TemporalioSamples.NexusStandaloneActivity.csproj", "{4D8C9F9B-F8E3-4160-9286-32C3966A0125}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.Gcp.CloudRun.Id", "src\Gcp\CloudRun\Id\TemporalioSamples.Gcp.CloudRun.Id.csproj", "{995322ED-0CF3-40F5-B521-380A9591DACC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -815,6 +817,18 @@ Global
{4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x64.Build.0 = Release|Any CPU
{4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x86.ActiveCfg = Release|Any CPU
{4D8C9F9B-F8E3-4160-9286-32C3966A0125}.Release|x86.Build.0 = Release|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x64.Build.0 = Debug|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Debug|x86.Build.0 = Debug|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x64.ActiveCfg = Release|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x64.Build.0 = Release|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x86.ActiveCfg = Release|Any CPU
+ {995322ED-0CF3-40F5-B521-380A9591DACC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..f5652aa
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Gcp/CloudRun/Id/Activities.cs b/src/Gcp/CloudRun/Id/Activities.cs
new file mode 100644
index 0000000..9004a8b
--- /dev/null
+++ b/src/Gcp/CloudRun/Id/Activities.cs
@@ -0,0 +1,14 @@
+namespace TemporalioSamples.Gcp.CloudRun.Id;
+
+using Microsoft.Extensions.Logging;
+using Temporalio.Activities;
+
+public static class Activities
+{
+ [Activity]
+ public static string SayHello(string name)
+ {
+ ActivityExecutionContext.Current.Logger.LogInformation("SayHello activity: {Name}", name);
+ return $"Hello, {name}!";
+ }
+}
diff --git a/src/Gcp/CloudRun/Id/Dockerfile b/src/Gcp/CloudRun/Id/Dockerfile
new file mode 100644
index 0000000..923fe5a
--- /dev/null
+++ b/src/Gcp/CloudRun/Id/Dockerfile
@@ -0,0 +1,14 @@
+# syntax=docker/dockerfile:1
+# Build from the repo root: docker build -f src/Gcp/CloudRun/Id/Dockerfile -t .
+FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
+WORKDIR /src
+COPY global.json Directory.Build.props Directory.Packages.props .editorconfig nuget.config ./
+COPY src/Gcp/CloudRun/Id/ ./src/Gcp/CloudRun/Id/
+RUN dotnet publish src/Gcp/CloudRun/Id/TemporalioSamples.Gcp.CloudRun.Id.csproj -c Release -o /app
+
+FROM mcr.microsoft.com/dotnet/runtime:8.0
+RUN useradd --create-home --uid 10001 worker
+WORKDIR /app
+COPY --from=build --chown=worker:worker /app ./
+USER 10001
+ENTRYPOINT ["dotnet", "TemporalioSamples.Gcp.CloudRun.Id.dll"]
diff --git a/src/Gcp/CloudRun/Id/Program.cs b/src/Gcp/CloudRun/Id/Program.cs
new file mode 100644
index 0000000..1b992d9
--- /dev/null
+++ b/src/Gcp/CloudRun/Id/Program.cs
@@ -0,0 +1,69 @@
+using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging;
+using Temporalio.Client;
+using Temporalio.Extensions.Gcp.CloudRun.Id;
+using Temporalio.Worker;
+using TemporalioSamples.Gcp.CloudRun.Id;
+
+// @@@SNIPSTART dotnet-cloud-run-id
+var address = GetEnvironmentVariable("TEMPORAL_ADDRESS") ?? "localhost:7233";
+var temporalNamespace = GetEnvironmentVariable("TEMPORAL_NAMESPACE") ?? "default";
+var taskQueue = GetEnvironmentVariable("TEMPORAL_TASK_QUEUE") ?? "cloud-run-worker-sample";
+
+using var loggerFactory = LoggerFactory.Create(builder => builder.
+ AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] ").
+ SetMinimumLevel(LogLevel.Information));
+var logger = loggerFactory.CreateLogger("CloudRunId");
+
+// Derives the client identity "{instanceId}@{revision}" from Cloud Run metadata at connect time.
+var clientOptions = new TemporalClientConnectOptions(address)
+{
+ Namespace = temporalNamespace,
+ LoggerFactory = loggerFactory,
+ Plugins = new[] { new CloudRunIdPlugin() },
+};
+
+var client = await TemporalClient.ConnectAsync(clientOptions);
+// @@@SNIPEND
+var metadata = await GoogleCloudRunMetadata.FetchAsync();
+logger.LogInformation("Cloud Run identity: {Identity}", metadata.Identity);
+
+var workerOptions = new TemporalWorkerOptions(taskQueue).
+ AddWorkflow().
+ AddActivity(Activities.SayHello);
+
+using var worker = new TemporalWorker(client, workerOptions);
+
+// Cloud Run sends SIGTERM before stopping an instance; shut down gracefully on that and Ctrl+C.
+using var cancellationSource = new CancellationTokenSource();
+Console.CancelKeyPress += (_, eventArgs) =>
+{
+ eventArgs.Cancel = true;
+ cancellationSource.Cancel();
+};
+using var sigterm = PosixSignalRegistration.Create(
+ PosixSignal.SIGTERM,
+ context =>
+ {
+ context.Cancel = true;
+ cancellationSource.Cancel();
+ });
+
+logger.LogInformation(
+ "Worker started on task queue '{TaskQueue}' against {Address} (namespace '{Namespace}').",
+ taskQueue,
+ address,
+ temporalNamespace);
+try
+{
+ await worker.ExecuteAsync(cancellationSource.Token);
+}
+catch (OperationCanceledException)
+{
+ logger.LogInformation("Shutdown signal received; worker stopped.");
+}
+
+static string? GetEnvironmentVariable(string name) =>
+ Environment.GetEnvironmentVariable(name) is { } value && !string.IsNullOrWhiteSpace(value)
+ ? value
+ : null;
diff --git a/src/Gcp/CloudRun/Id/README.md b/src/Gcp/CloudRun/Id/README.md
new file mode 100644
index 0000000..90f832a
--- /dev/null
+++ b/src/Gcp/CloudRun/Id/README.md
@@ -0,0 +1,49 @@
+# Cloud Run Id
+
+Run a Temporal Worker on a
+[Google Cloud Run worker pool](https://cloud.google.com/run/docs/deploy-worker-pools) and derive its
+identity from Cloud Run instance metadata using the `Temporalio.Extensions.Gcp.CloudRun.Id` package.
+
+`Program.cs` registers a `CloudRunIdPlugin` on `TemporalClientConnectOptions.Plugins`. At connect
+time the plugin sets the client `Identity` to `{instanceId}@{revision}` from the Cloud Run metadata
+server and environment, and every Worker created from the client inherits it. A greeting workflow and
+activity poll the task queue until the container stops. Worker pools run instances that take no HTTP
+traffic, which suits a Temporal Worker; the same code also works on a Cloud Run service.
+
+> The extension is not on nuget.org yet, so the sample restores it from the committed
+> `local-packages/` feed (see `nuget.config`) until it ships.
+
+## Prerequisites
+
+- A Temporal server the worker pool can reach
+- A Google Cloud project with billing and the Cloud Run and Artifact Registry APIs enabled
+- [`gcloud`](https://cloud.google.com/sdk/docs/install), authenticated with the project set
+- The [Temporal CLI](https://docs.temporal.io/cli) and .NET 8
+
+## Deploy
+
+Build the image from the repo root (the Dockerfile pulls in the shared props and the local feed),
+push it, then deploy a worker pool. Worker pools may require the `beta` track.
+
+```bash
+export REGION=us-central1 WORKER_POOL=temporal-dotnet-worker
+export WORKER_IMAGE=$REGION-docker.pkg.dev/$(gcloud config get-value project)/samples/cloud-run-id
+
+docker build -f src/Gcp/CloudRun/Id/Dockerfile -t "$WORKER_IMAGE" . && docker push "$WORKER_IMAGE"
+
+gcloud run worker-pools deploy "$WORKER_POOL" --image "$WORKER_IMAGE" --region "$REGION" \
+ --set-env-vars TEMPORAL_ADDRESS=,TEMPORAL_NAMESPACE=,TEMPORAL_TASK_QUEUE=cloud-run-worker-sample
+```
+
+Cloud Run sets `CLOUD_RUN_WORKER_POOL` and `CLOUD_RUN_REVISION`, which the plugin turns into the
+client identity. The sample connects in plaintext; for Temporal Cloud add API key / mTLS to
+`Program.cs`.
+
+## Run a workflow
+
+```bash
+temporal workflow execute --task-queue cloud-run-worker-sample --type SampleWorkflow --input '"Cloud Run"'
+```
+
+A successful run returns `"Hello, Cloud Run!"`. Delete the pool with
+`gcloud run worker-pools delete "$WORKER_POOL" --region "$REGION"`.
diff --git a/src/Gcp/CloudRun/Id/SampleWorkflow.workflow.cs b/src/Gcp/CloudRun/Id/SampleWorkflow.workflow.cs
new file mode 100644
index 0000000..f1db85d
--- /dev/null
+++ b/src/Gcp/CloudRun/Id/SampleWorkflow.workflow.cs
@@ -0,0 +1,19 @@
+namespace TemporalioSamples.Gcp.CloudRun.Id;
+
+using Microsoft.Extensions.Logging;
+using Temporalio.Workflows;
+
+[Workflow]
+public class SampleWorkflow
+{
+ [WorkflowRun]
+ public async Task RunAsync(string name)
+ {
+ Workflow.Logger.LogInformation("SampleWorkflow started: {Name}", name);
+ var result = await Workflow.ExecuteActivityAsync(
+ () => Activities.SayHello(name),
+ new() { StartToCloseTimeout = TimeSpan.FromSeconds(10) });
+ Workflow.Logger.LogInformation("SampleWorkflow completed: {Result}", result);
+ return result;
+ }
+}
diff --git a/src/Gcp/CloudRun/Id/TemporalioSamples.Gcp.CloudRun.Id.csproj b/src/Gcp/CloudRun/Id/TemporalioSamples.Gcp.CloudRun.Id.csproj
new file mode 100644
index 0000000..8cc6af6
--- /dev/null
+++ b/src/Gcp/CloudRun/Id/TemporalioSamples.Gcp.CloudRun.Id.csproj
@@ -0,0 +1,11 @@
+
+
+
+ Exe
+
+
+
+
+
+
+
diff --git a/src/Gcp/CloudRun/Id/local-packages/Temporalio.Extensions.Gcp.CloudRun.Id.1.19.0.nupkg b/src/Gcp/CloudRun/Id/local-packages/Temporalio.Extensions.Gcp.CloudRun.Id.1.19.0.nupkg
new file mode 100644
index 0000000..36e8cce
Binary files /dev/null and b/src/Gcp/CloudRun/Id/local-packages/Temporalio.Extensions.Gcp.CloudRun.Id.1.19.0.nupkg differ