Skip to content

💥 activity describe: return full DescribeActivityExecutionResponse#1128

Open
dandavison wants to merge 1 commit into
mainfrom
saa-describe
Open

💥 activity describe: return full DescribeActivityExecutionResponse#1128
dandavison wants to merge 1 commit into
mainfrom
saa-describe

Conversation

@dandavison

@dandavison dandavison commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What changed?

Change temporal activity describe to output the entire DescribeWorkflowExecutionResponse

Why

temporal activity describe printed only the nested ActivityExecutionInfo. This was different from temporal workflow describe, which prints the whole DescribeWorkflowExecutionResponse.

Checklist

Stability
non-GA API

Tests

go test ./internal/temporalcli/ -run 'TestSharedServerSuite/TestActivity_Describe'

Manual tests

Happy path

Setup

./temporal server start-dev \
    --dynamic-config-value 'activity.enableCallbacks=true' \
    --dynamic-config-value 'callback.allowedAddresses=[{"Pattern":"*","AllowInsecure":true}]'
SAA workfload
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.10"
# dependencies = ["temporalio"]
# ///
"""
Generate Standalone Activity traffic against a Temporal Cloud (test) namespace, to validate
namespace_open_activities_count. Runs a worker and a client in the same process.


Usage:
  ./saa_traffic.py [num_activities] [sleep_seconds] [run_seconds]
"""

import asyncio
import os
import sys
import uuid
from concurrent.futures import ThreadPoolExecutor

from google.protobuf.duration_pb2 import Duration
from temporalio import activity
from temporalio.api.common.v1 import ActivityType, Callback, Link, Payloads
from temporalio.api.taskqueue.v1 import TaskQueue
from temporalio.api.workflowservice.v1 import StartActivityExecutionRequest
from temporalio.client import Client
from temporalio.worker import Worker

TASK_QUEUE = "saa-metrics-test-task-queue"


@activity.defn
def slow_activity(sleep_seconds: int) -> str:
    activity.logger.info(f"sleeping for {sleep_seconds}s")
    import time

    time.sleep(sleep_seconds)
    return "done"


async def main() -> None:
    num_activities = int(sys.argv[1]) if len(sys.argv) > 1 else 5
    sleep_seconds = int(sys.argv[2]) if len(sys.argv) > 2 else 120
    run_seconds = int(sys.argv[3]) if len(sys.argv) > 3 else 240

    address = os.environ.get("TEMPORAL_ADDRESS", "localhost:7233")
    namespace = os.environ.get("TEMPORAL_NAMESPACE", "default")
    api_key = os.environ.get("TEMPORAL_API_KEY")

    client = await Client.connect(
        address,
        namespace=namespace,
        api_key=api_key,
        tls=bool(api_key),
    )

    worker = Worker(
        client,
        task_queue=TASK_QUEUE,
        activities=[slow_activity],
        activity_executor=ThreadPoolExecutor(num_activities),
    )
    worker_task = asyncio.create_task(worker.run())

    callback_url = os.environ.get(
        "TEMPORAL_CALLBACK_URL", "http://localhost/saa-callback"
    )

    handles = []
    for i in range(num_activities):
        activity_id = f"saa-metrics-test-{i}"
        request = StartActivityExecutionRequest(
            namespace=namespace,
            identity=client.identity,
            request_id=str(uuid.uuid4()),
            activity_id=activity_id,
            activity_type=ActivityType(name=slow_activity.__name__),
            task_queue=TaskQueue(name=TASK_QUEUE),
            input=Payloads(
                payloads=await client.data_converter.encode([sleep_seconds])
            ),
            start_to_close_timeout=Duration(seconds=sleep_seconds + 30),
            completion_callbacks=[Callback(nexus=Callback.Nexus(url=callback_url))],
            links=[
                Link(
                    activity=Link.Activity(
                        namespace=namespace,
                        activity_id=f"saa-metrics-test-{max(i - 1, 0)}",
                    )
                )
            ],
        )
        resp = await client.workflow_service.start_activity_execution(request)
        print(f"started {activity_id} run={resp.run_id}")
        handles.append(client.get_activity_handle(activity_id, run_id=resp.run_id))

    async def report_open_count() -> None:
        while True:
            count = await client.count_activities("ExecutionStatus='Running'")
            print(f"[local check] open activities: {count.count}")
            await asyncio.sleep(15)

    report_task = asyncio.create_task(report_open_count())

    await asyncio.sleep(run_seconds)

    report_task.cancel()
    worker_task.cancel()
    for handle in handles:
        try:
            print(f"{handle.id}: {await handle.result()}")
        except Exception as e:
            print(f"{handle.id}: {e}")


if __name__ == "__main__":
    asyncio.run(main())
cli(saa-describe) ./temporal activity describe -a saa-metrics-test-1
Activity Execution Info:
  ActivityId            saa-metrics-test-1
  RunId                 019f658a-181d-7f13-8ce2-22c0a7cad85f
  Type                  slow_activity
  Status                Running
  RunState              Scheduled
  TaskQueue             saa-metrics-test-task-queue
  StartToCloseTimeout   5m30s
  Attempt               1
  ScheduleTime          4 minutes ago
  StateTransitionCount  1

Callbacks:
   State   Attempt  RegistrationTime  NextAttemptScheduleTime  BlockedReason  LastAttemptFailure
  Standby        0  4 minutes ago
cli(saa-describe) ./temporal activity describe -a saa-metrics-test-1 -o json
{
  "runId": "019f658a-181d-7f13-8ce2-22c0a7cad85f",
  "info": {
    "activityId": "saa-metrics-test-1",
    "runId": "019f658a-181d-7f13-8ce2-22c0a7cad85f",
    "activityType": {
      "name": "slow_activity"
    },
    "status": "ACTIVITY_EXECUTION_STATUS_RUNNING",
    "runState": "PENDING_ACTIVITY_STATE_SCHEDULED",
    "taskQueue": "saa-metrics-test-task-queue",
    "scheduleToCloseTimeout": "0s",
    "scheduleToStartTimeout": "0s",
    "startToCloseTimeout": "330s",
    "heartbeatTimeout": "0s",
    "retryPolicy": {
      "initialInterval": "1s",
      "backoffCoefficient": 2,
      "maximumInterval": "100s"
    },
    "attempt": 1,
    "scheduleTime": "2026-07-15T11:29:36.798007Z",
    "stateTransitionCount": "1",
    "stateSizeBytes": "845",
    "searchAttributes": {}
  },
  "longPollToken": "CiQwMTlmNjU4Ny01NjRlLTc5YmMtYjc3NS01MGQxYTc5MTcxNTESEnNhYS1tZXRyaWNzLXRlc3QtMRokMDE5ZjY1OGEtMTgxZC03ZjEzLThjZTItMjJjMGE3Y2FkODVmIOKwg7UMKgIQAToCEAE=",
  "callbacks": [
    {
      "trigger": {
        "activityClosed": {}
      },
      "info": {
        "callback": {
          "nexus": {
            "url": "http://localhost/saa-callback"
          }
        },
        "registrationTime": "2026-07-15T11:29:36.798007Z",
        "state": "CALLBACK_STATE_STANDBY"
      }
    }
  ]
}

Error case
I verified that no callback info is emitted when the activity has no callback:

{
  "runId": "019f657b-db53-7b1f-becf-23e972cd3b2a",
  "info": {
    "activityId": "saa-metrics-test-0",
    "runId": "019f657b-db53-7b1f-becf-23e972cd3b2a",
    "activityType": {
      "name": "slow_activity"
    },
    "status": "ACTIVITY_EXECUTION_STATUS_RUNNING",
    "runState": "PENDING_ACTIVITY_STATE_STARTED",
    "taskQueue": "saa-metrics-test-task-queue",
    "scheduleToCloseTimeout": "0s",
    "scheduleToStartTimeout": "0s",
    "startToCloseTimeout": "150s",
    "heartbeatTimeout": "0s",
    "retryPolicy": {
      "initialInterval": "1s",
      "backoffCoefficient": 2,
      "maximumInterval": "100s"
    },
    "lastStartedTime": "2026-07-15T11:14:03.735583Z",
    "attempt": 1,
    "scheduleTime": "2026-07-15T11:14:03.731770Z",
    "lastWorkerIdentity": "[email protected]",
    "stateTransitionCount": "2",
    "searchAttributes": {}
  },
  "longPollToken": "CiQwMTlmNjU3Yi1hN2JiLTdhNDQtODJhNi0xMzMyMWJmNmYyMTMSEnNhYS1tZXRyaWNzLXRlc3QtMBokMDE5ZjY1N2ItZGI1My03YjFmLWJlY2YtMjNlOTcyY2QzYjJhIOKwg7UMKgIQAjoCEAE="
}

The text version doesn't print out the Callbacks: header.

`temporal activity describe` printed only the nested ActivityExecutionInfo
(via the SDK's handle.Describe, which discards the rest of the response),
so the response-level fields — run_id, callbacks, long_poll_token,
input/outcome — were unreachable. This diverged from `temporal workflow
describe`, which prints the whole DescribeWorkflowExecutionResponse.

Call DescribeActivityExecution directly (like the other activity
subcommands already do) and print the full response: JSON/raw now carry
callbacks and the info nests under "info"; text mode adds a Callbacks
section. IncludeLastFailure is now set so LastFailure actually populates.

Run tests:
  go test ./internal/temporalcli/ -run 'TestSharedServerSuite/TestActivity_Describe'
@dandavison dandavison requested a review from a team as a code owner July 15, 2026 11:45
@dandavison dandavison changed the title activity describe: return full DescribeActivityExecutionResponse 💥 activity describe: return full DescribeActivityExecutionResponse Jul 15, 2026
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.

2 participants