Skip to content

[pull] master from DataDog:master#637

Merged
pull[bot] merged 6 commits into
ConnectionMaster:masterfrom
DataDog:master
Jul 3, 2026
Merged

[pull] master from DataDog:master#637
pull[bot] merged 6 commits into
ConnectionMaster:masterfrom
DataDog:master

Conversation

@pull

@pull pull Bot commented Jul 3, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

NouemanKHAL and others added 6 commits July 3, 2026 09:29
* feat(nutanix): add collect_resource_ids_as_tags option

Adds an opt-in instance option that attaches Nutanix resource extId
values as tags on cluster, host, and VM infrastructure metrics, so
Datadog metrics can be correlated with specific Nutanix resources.

Defaults to false because the tags are high cardinality (one unique
value per resource).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(nutanix): add changelog entry for #24303

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
)

* Bundle per-job correlation into BatchFinished via BatchJobResult

Move the job spec / workflow-run / artifact-directory correlation out of the
gatherer and into the TaskTestRunner producer, shipping it as a single enriched
per-job BatchJobResult on BatchFinished.

- BatchJob.artifact_name(): deterministic, collision-free, sanitized artifact
  name derived solely from the job's frozen fields. Replaces the gatherer's
  delimiter-bounded token match as the way a job's artifact directory is resolved.
- BatchJobResult bundles the BatchJob spec, the matched WorkflowJob (or None),
  and the resolved artifact path (or None).
- BatchFinished now carries batch_jobs; the runner joins each job to its
  workflow job by name and resolves its artifact directory by artifact name,
  tolerating missing API matches and missing artifacts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* updates

* updates

* updates

* updates

* Address review comments: BatchJobResult.correlate factory and focused tests

- Move correlation logic from TaskTestRunner._build_batch_jobs into a
  BatchJobResult.correlate static factory so it is testable without the processor.
- _download_artifacts returns only the artifact-name -> path map; artifacts_path
  is taken from the runner options.
- test_messages.py: explicit-args batch_job factory, drop the tautological
  determinism test, add direct correlate tests.
- test_task_test_runner.py: drop underscore-prefixed helpers and split the
  monolithic happy-path test into one-concern-per-test cases via a shared helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add changelog entry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Change changelog entry type to fixed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* kafka_consumer: add Confluent Cloud Connect support

Collect connector metrics and configuration events from the Confluent
Cloud Connect REST API via the kafka_connect_confluent_cloud_environment_id,
kafka_connect_confluent_cloud_cluster_id, and kafka_connect_confluent_cloud_url
options.

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

* kafka_consumer: stop placeholder Confluent Cloud IDs becoming config defaults

Add display_default: null to kafka_connect_confluent_cloud_environment_id
and kafka_connect_confluent_cloud_cluster_id so the example IDs are not baked
into defaults.py and conf.yaml.example. Also document that authentication uses
kafka_connect_username/password (Confluent Cloud API key/secret).

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

* kafka_consumer: harden Confluent Cloud URL fallback and dedupe gating predicate

Coerce an explicit-null kafka_connect_confluent_cloud_url to the default base
URL to avoid AttributeError on None.rstrip(). Add a confluent_cloud_configured
property on KafkaConfig and use it in both _collect_connect_status and
_confluent_cloud_key so the gating rule lives in one place.

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

* kafka_consumer: test Confluent Cloud path emits metrics and config events

Add test_confluent_cloud_emits_metrics_and_config_events asserting that with
Confluent Cloud configured the run emits kafka.connector.count=2 and connector
config events whose connect_url targets the Confluent Cloud endpoint.

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

* kafka_consumer: support Confluent Cloud Connect generically via response-driven expand fallback

Remove the Confluent-Cloud-specific config options and collection code. Confluent
Cloud's Connect REST API is OSS-shaped and reachable by pointing kafka_connect_url
at the Cloud base path with a Cloud API key as basic auth, so it needs no special
handling beyond tolerating its single-expand behavior.

Make _collect_rest response-driven: it issues one combined expand=info&status
request and, only when a section comes back null (as Confluent Cloud does, honoring
one expand value per request), fetches the missing section and merges it. OSS
workers return both sections in the combined call, so behavior and request count
are unchanged for self-hosted Connect. Also tolerate explicit null info/status
values when emitting metrics and config events.

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

* Apply review fixes for Confluent Cloud Connect support

- Guard None connector entries in both _emit_connector_metrics and
  _emit_connector_config_events so a null entry no longer crashes a URL's
  whole collection.
- _merge_expand: only assign a section when the fallback value is not None,
  avoiding overwriting with an explicit None.
- Supplementary expand fetches now degrade gracefully: each is wrapped in
  try/except that logs a warning and emits partial data instead of marking
  the endpoint unreachable.
- Add type hints to the new _merge_expand method and _fetch closure.
- Clarify the Confluent Cloud auth note in spec.yaml: API key (ID) goes in
  kafka_connect_username and the secret in kafka_connect_password; regenerate
  conf.yaml.example.
- Strengthen test_single_expand_sections_merged to assert the merged info
  surfaces in the config event, and add an OSS single-request invariant test.

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

* kafka_consumer: condense verbose connector comments to one line

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

* kafka_consumer: retry combined expand as single value for Confluent Cloud

Confluent Cloud's Connect v1 expansions endpoint ignores a combined
expand list (serialized as repeated query params) and returns the
unexpanded connector-name list instead, so the response fell into the
pre-2.3 bail-out path before the single-expand fallback could run.
Retry with one expand value before giving up.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… fails (#24263)

* Don't abort kafka_consumer when one partition's high-watermark lookup fails

The high-watermark offset collection issues a single batched
offsets_for_times call for all topic-partitions. If any partition cannot
be resolved at all (e.g. a leaderless/offline RF-0 or mid-deletion
topic), librdkafka raises a KafkaException (UNKNOWN_TOPIC_OR_PART) for
the entire batch call, which propagates up and aborts the whole check so
no metrics are collected.

Fall back to per-partition lookups when the batched call fails, skipping
only the partitions that still raise, so healthy partitions are still
collected and the check is not aborted.

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

* Add changelog entry for PR #24263

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

* Fetch high-watermark offsets via AdminClient.list_offsets

Replace the resilient-but-clunky offsets_for_times approach with
AdminClient.list_offsets, which returns a future per partition. Errored
partitions (e.g. leaderless/offline topics that raise
UNKNOWN_TOPIC_OR_PART) are skipped individually, so healthy partitions
are still collected without a wholesale raise or a per-partition retry
loop.

list_offsets is also the purpose-built API for latest/earliest offsets,
unlike offsets_for_times with sentinel timestamp values.

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

* Apply review fixes: degrade highwater offset collection gracefully

Mirror cluster_metadata.fetch_earliest_offsets in get_partition_offsets:
broaden the per-future handler to catch any Exception so one bad
partition does not abort the loop, and wrap the outer list_offsets
call so a request/broker-level failure logs a warning and returns []
instead of aborting the whole highwater collection.

Strengthen unit tests: assert list_offsets is called with
isolation_level=READ_UNCOMMITTED and the request timeout, cover the
non-Kafka per-partition error and request-level failure paths, and add
an empty-partitions test that issues no request.

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

* kafka_consumer: shorten get_partition_offsets docstring

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

* Re-raise on full-request list_offsets failures in get_partition_offsets

A request-level failure was being swallowed into an empty result, so
highwater offsets silently defaulted to 0 for every partition and
corrupted partition.size/topic.size metrics instead of skipping them.
Only individual partition lookup failures should be tolerated; a
full-request failure should abort highwater collection like before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Extend `release branch tag` with `--release`, `--ref`, `--rc N`, and `--yes`

The command previously required the user to be on the release branch and only
let them pick between `--rc` and `--final`. The new flags make it possible to
tag a release branch from anywhere, pin or auto-suggest the RC number, and tag
an arbitrary commit instead of the branch tip:

- `--release/-r <X.Y[.x]>` selects the target release branch; if not on it, a
  worktree is created at `.worktrees/release-tag/<branch>` from
  `origin/<branch>` and torn down on success.
- `--ref <commit>` tags the given commit (must be an ancestor of
  `origin/<branch>`) instead of the branch tip.
- `--rc` accepts an optional value: `--rc` alone auto-suggests, `--rc N` pins.
  Warns when N skips ahead of the next available RC.
- `--yes/-y` skips all yes/no confirms.

* Add changelog entry

* Apply round 1 review feedback and fix mypy lint

- `GitRepository.tag()` now uses its `message` parameter instead of the
  tag name (pre-existing bug: `--message` was always set to `value`).
- Compute `current_branch` once and thread it into `_resolve_target_branch`
  to avoid the duplicate `git rev-parse` subprocess call.
- Move worktree setup inside the try/finally so any failure between
  worktree creation and teardown still triggers the inspection warning.
- Narrow the OSError catch to just `git.tag` / `git.push` so other failures
  surface with their own attributable messages.
- `_branch_exists_on_origin` no longer swallows OSError as "branch missing"
  — split into `_ensure_branch_on_origin` that distinguishes a real
  ls-remote failure from an empty result.
- Confirmation prompt for `--ref` shows the resolved commit SHA, not the
  raw user input, so the user can verify what `rev-parse` resolved to.
- Renamed `_create_or_refresh_worktree` to `_create_worktree` (it only
  adds) and the failure message now hints at the manual cleanup command.
- Added an `exit_code` assertion to `test_no_worktree_when_already_on_target_branch`.
- Input-validation helpers now raise `click.UsageError` so mypy follows
  the `NoReturn` semantics; updated the two affected tests to assert
  non-zero exit instead of exit code 1 (UsageError exits with 2).

* Apply round 2 review feedback

- Drop unused `app` arg from `_parse_rc_value` (it raises `click.UsageError`
  directly and never needed application state).
- Include the underlying git error text in the `--ref` failure messages
  for both `rev-parse` and `merge-base --is-ancestor`.
- Remove dead post-`range` guard in `_warn_on_rc_gap`.
- Annotate `_check_open_prs` return as `list[PullRequest]`.
- Rename changelog entry from `.added` to `.changed`: the new confirm
  prompt on a release branch is a behavior change for non-interactive
  callers that piped a single `y`.
- Replace the `capture.return_value` fixture default with a side_effect
  callable that dispatches on the first argument, so new code paths that
  call `capture()` without an explicit override don't silently inherit
  the `ls-remote` payload.
- Use a `_make_ref_dispatcher` helper in `--ref` tests so the mocked
  responses are keyed by subcommand instead of call order.
- Extract `_worktree_subcommands(git, sub)` helper to dedupe the
  worktree-call assertions.

* Apply round 3 review feedback

- `--final` + `--rc` now raises `click.UsageError` (exit code 2) to match
  the other CLI-validation errors in the command.
- Change the `RC_AUTO` sentinel to a private, unguessable string so
  `--rc auto` is no longer silently equivalent to bare `--rc`.
- Merge `_resolve_tag_ref`'s two try/except blocks; the branch that
  picks the abort message keys off whether `commit_sha` was bound,
  which avoids the possibly-unbound warning some type-checkers emit.
- `_warn_on_rc_gap` now uses `app.display_warning` to match the other
  new helpers in this PR.
- Test additions: full call-shape assertions on the `worktree add` /
  `worktree remove` invocations, plus a new test that covers the
  `_create_worktree` abort path and its manual-cleanup hint.

* Normalize backslashes in worktree path assertions for Windows

* Extract helpers from `tag()` body to read as a recipe

The command body had four logical phases interleaved with the worktree
lifecycle. Each phase is now its own helper:

- `_prepare_working_dir` decides between pull-in-place and worktree creation,
  returns `(git, working_dir, worktree_created)`.
- `_warn_if_build_agent_yaml_stale` surfaces the recovery warning and
  returns whether the workflow dispatch is needed.
- `_compute_new_tag` owns the existing-tag collection, RC resolution,
  and all RC validations (bounds, exists, gap, backward move).
- `_confirm_and_push_tag` owns the open-PR check, the final confirm
  prompt, and the `git tag` / `git push` pair.

The main `tag()` body shrinks from ~108 to ~35 lines and reads top-to-bottom
as the steps of a tagging operation. The worktree `try/finally` stays in
the main body — a future change can replace it with a context manager once
worktree management is consolidated across the codebase.

* Tag directly against `origin/<branch>` instead of via a worktree

The previous design checked out the target branch in a worktree
under `.worktrees/release-tag/<branch>/` and ran the tag command
from there. The worktree was only ever needed to read
`.gitlab/build_agent.yaml` from the right commit, since every
other step (listing tags, validating `--ref`, creating and pushing
the tag) keys off ref/object names that any clone resolves the
same way.

Read the YAML via `git show <ref>:.gitlab/build_agent.yaml` and
pass an explicit `ref` to `git tag` (`origin/<target>` by default,
the resolved commit when `--ref` is supplied). The user's local
checkout and branches are never touched.

Side effects:
- Drops `WORKTREE_BASE`, `_create_worktree`, `_remove_worktree`,
  `_prepare_working_dir`, and the worktree `try/finally`.
- Closes a silent-data-loss bug: `git worktree add -B <branch>`
  force-resets a pre-existing local `<branch>` to the remote tip,
  orphaning any local-only commits on it.
- Reads the YAML at the actual commit being tagged when `--ref` is
  supplied, rather than at the branch tip.
- Removes the implicit `git pull origin <branch>` that the
  no-worktree path used to do — tagging no longer mutates local
  branch state under any flag combination.

* Apply round 4 review feedback

- Move the `< 1` guard into `_parse_rc_value` so `--rc 0` is rejected
  via `click.UsageError` (exit code 2) like every other `--rc` parse
  failure, instead of via `app.abort` (exit code 1) after the prompt
  branch. The interactive-prompt branch still aborts on `< 1`, which
  is now the only path that reaches that check.
- Add `git` as an explicit parameter to `_ensure_branch_on_origin` to
  match the other git-using helpers.
- Tighten the worktree-regression guard in tests: filter both `run`
  and `capture` calls, since `git worktree` operations can go through
  either entry point.
- `_make_ref_dispatcher` now delegates to `_capture_dispatch` for
  subcommands it doesn't explicitly handle, removing the duplicated
  `ls-remote` literal.

---------

Co-authored-by: Alexey Pilyugin <alexey.pilyugin@datadoghq.com>
* Add ddev release test-agent command

Dispatch both .github/workflows/test-agent.yml and test-agent-windows.yml
against a release branch or tag, with Agent image resolution and validation
against registry.datadoghq.com. The command resolves the latest published
*-rc.N tag for a branch input, validates Linux and Windows (servercore)
manifests, shows a confirmation panel, then fires both workflow_dispatch
calls in parallel via the async GitHub client.

Extends AsyncGitHubClient.create_workflow_dispatch with a typed
return_run_details kwarg (overloads) so the new 200 response shape
(workflow_run_id, run_url, html_url) is parsed into a WorkflowDispatchResult
when requested, and the result panel links directly to each run.

* Add changelog entry

* Apply review feedback: tighten typing, broaden registry handling, expand tests

- Rename _MANIFEST_ACCEPT to MANIFEST_ACCEPT per AGENTS.md (no underscore prefix on module constants).
- Move asyncio import to module level; drop duplicate import.
- Narrow _extract_run_urls signature to Sequence[GitHubResponse[WorkflowDispatchResult] | BaseException]; remove three type:ignore comments and the unused owner/repo/ref params.
- Surface the originating exception (__cause__) in the abort message after a dispatch failure.
- Combine messages when both Linux and Windows dispatches fail.
- tag.lstrip('v') -> tag.removeprefix('v') for accurate intent.
- Document REPO_OWNER design choice with a short comment.
- Add one-line docstrings to manifest_url and tags_list_url.
- Request a large page (n=10000) when listing registry tags; the Agent registry has many years of tags and the default page may not include the current release cycle.
- Type-annotate test fixtures and test functions.
- Parametrize test_branch_resolves_latest_rc over workflow_id; add a mirror test for the Linux-fails partial-dispatch case; add a both-fail case; cover 401/403/503 in the manifest error test; cover null and missing 'tags' key in the tags-list parser.

* Apply round-2 review feedback + ruff 0.11.10 format

Review feedback:
- Pass inputs dict through to _print_plan so the plan display is derived
  from the same source the dispatch sees (no more hardcoded 'true'/'false'
  drifting from the inputs values).
- Use string values ('true', 'false') for type:boolean workflow inputs to
  match what the GitHub workflow_dispatch API documents.
- Replace assert-based type narrowing in _extract_run_urls with nested
  isinstance checks so the flow stays sound under python -O.
- Rename surviving_label -> failing_label in the parametrized partial-dispatch
  test (the assertion targets the failing side's message, not the surviving).

CI fix:
- Reformat with ruff 0.11.10 (the version pinned by the ddev CI workflow);
  my local hatch env shipped 0.15.11 which produced minor multi-line layout
  differences. Affects test-agent.yml lint for both Linux and Windows
  matrices.

* Harden ddev release test-agent error handling

- Abort early when github.token is empty instead of leaking the
  AsyncGitHubClient ValueError out of asyncio.run.
- Abort with a friendly message when the public Agent registry returns
  a non-404 HTTP error from manifest_exists or list_agent_rc_tags,
  rather than surfacing the raw httpx traceback.
- Narrow the inputs dict to dict[str, str] all the way through
  _dispatch_both / _dispatch_both_async; the workflow_dispatch API
  rejects non-string values, so an accidental non-string value now
  becomes a type error at the closest boundary to the API call.
- Drop the cause-formatting suffix on the dispatch-failure abort; the
  RuntimeError messages already embed the underlying error so the
  cause would render twice.
- Drop the unused ref parameter from _print_result.
- Add tests covering timeout and connection-error propagation from
  the registry helpers.

* Hardcode dispatch target to DataDog/integrations-core

- Introduce REPO_NAME='integrations-core' constant alongside REPO_OWNER
  and use it in the workflow_dispatch call, so a user with ddev pointed
  at integrations-extras/marketplace/a fork doesn't silently dispatch
  to DataDog/<wrong-repo>. Both test-agent.yml workflows only exist on
  integrations-core, matching the existing owner hardcoding rationale.
- Lift the duplicate inline 'import httpx' to a top-level import so
  the module's external dependencies are visible at a glance.
- Add tests for the empty-token guard and for the two registry HTTP
  error paths that translate to friendly app.abort messages.

* Extract docker_registry utility and tighten test-agent workflow checks

- Move generic Docker Registry v2 helpers (manifest probe, tag listing with
  Link: rel=next pagination) into ddev.utils.docker_registry so they can be
  reused by other release tooling. The agent-specific RC filter stays in
  cli.release.test_agent.registry as a thin wrapper.
- Read workflow files from origin/<branch> so a branch the user has not yet
  fetched no longer reads as a missing workflow file. Distinguish "file not
  in tree" from "ref not in local clone" from other git failures so the
  abort message points at the real problem.
- Move httpx and asyncio imports back inside the functions that use them;
  the registry module is lazy-imported, so the top-level imports were paid
  on every ddev invocation (including ddev --help).
- Use add_note to keep the Windows traceback attached when both dispatches
  fail. Wrap the result print in try/except/else so the success-only call
  site is obvious.
- Take versions (not full image refs) into _validate_images_exist; drop the
  rsplit round-trip.
- Note in the inputs dict that test-py2='false' on Windows is intentional.

* Lift asyncio import to module top

* Surface both errors when test-agent dispatches both fail

- Fold the Linux and Windows error reprs into the RuntimeError message so
  app.abort(str(e)) renders both. The previous add_note(...) approach
  stored the Windows error in __notes__, which str(exc) does not include,
  so the Windows side was silently dropped from the abort output.
- Re-raise CancelledError and other non-Exception BaseException subclasses
  from _extract_run_urls before treating results as dispatch failures.
  asyncio.gather(return_exceptions=True) captures cancellation into the
  result list; wrapping it in RuntimeError would hide flow-control intent.
- Pass the github token directly into _dispatch_both instead of threading
  the whole Application object through. Decouples the helper.
- Centralize the httpx.HTTPError -> app.abort translation in a
  _registry_errors context manager; removes the duplicated try/except in
  _resolve_version and _validate_images_exist and keeps the lazy httpx
  import in one place.
- Render the dispatch plan via display_info so it lands on stderr
  alongside the surrounding progress lines; piping the command no longer
  splits the pre-dispatch narrative across stdout and stderr.
- Replace the hand-rolled Link-header parser in docker_registry.list_tags
  with httpx.Response.links, which handles RFC 5988 quoting and
  multi-link rels correctly.
- Strengthen test_both_dispatches_fail_combine_messages to assert both
  Windows: and a count of 2 of the shared error repr so the regression
  cannot recur.

* Split test-agent helpers into validation/images/dispatch modules

Move the supporting logic for `ddev release test-agent` into sibling modules
so the command file reads as the orchestration story it tells:

- validation.py — input regex checks, git ref existence on origin, and
  workflow-file presence on the resolved ref (including the file-missing
  vs ref-not-fetched vs unknown-git-failure dispatch in the error path).
- images.py — RC version resolution, image ref construction, manifest
  existence checks, and the registry_errors context manager that
  translates httpx errors into clean abort messages.
- dispatch.py — the parallel workflow_dispatch orchestration. The async
  coroutine is nested inside dispatch_both so the reader sees the full
  flow in one function rather than bouncing between two near-empty
  stack frames. extract_run_urls keeps the partial/total-failure surface
  next to the dispatch.

`__init__.py` now contains only the Click command plus the small
`_print_plan`/`_print_result` display helpers. Every sibling module is
imported lazily inside the command body so `ddev --help` only pays for
`click` from this package.

Also narrow `AsyncGitHubClient.create_workflow_dispatch`'s `inputs`
parameter from `dict[str, Any] | None` to `dict[str, str] | None`
across both `@overload`s and the implementation. The workflow_dispatch
API contract is string-to-string (booleans are matched against the
lowercase string form), every in-tree caller already passes a
`dict[str, str]`, and the wider type silently accepted values that
would surface as runtime 422s from GitHub. The fake test client mirror
is updated to match.

* Auto-fetch the target ref and model branch/tag as a sum type

Make the user's `--branch` or `--tag` choice a typed `Branch | Tag` produced by
`validate_input`, and have every downstream helper take that `ReleaseTarget`
instead of `(branch: str | None, tag: str | None)`. This puts the "exactly one
is set" invariant in the type system and removes the type-narrowing `assert`s
that would otherwise turn into an `AssertionError` with no context if anyone
broke the invariant.

While at it, drop the `git ls-remote` probe + "please run `git fetch` and try
again" hint and fetch the ref ourselves. The new `fetch_target` runs
`git fetch --quiet --depth=1 origin refs/heads/<branch>:refs/remotes/origin/<branch>`
(or the equivalent `refs/tags/...:refs/tags/...` for `--tag`), which both confirms
the ref exists on origin and populates the local refs we need to read the
workflow files. If `git fetch` reports `couldn't find remote ref`, the abort
message stays the same as before (`Branch X not found on origin`); other git
errors surface verbatim through `Failed to fetch ... from origin: ...`.

Tests now mock `GitRepository.run` (the fetch) instead of `GitRepository.capture`
(the ls-remote). Two new tests pin the exact refspec the command must send so a
future refactor that breaks the fetch shape can't slip through. The
"please-fetch-first" test goes away; that branch is unreachable now.

* Define workflow names in validation; symmetric error shape; cover CancelledError

- Move WORKFLOW_LINUX/WORKFLOW_WINDOWS from dispatch.py to validation.py and
  have dispatch.py import them from validation. Inverts the previous arrow so
  the layer that runs first owns the constants; validation no longer pulls in
  dispatch's async/HTTP modules at import time.
- Make fetch_target's OSError handler use `if/else` for symmetry with
  verify_workflows_present_on_ref. Behavior unchanged; the two helpers now
  read identically instead of relying on `app.abort`'s `NoReturn` to keep the
  trailing abort unreachable.
- Add direct unit tests for extract_run_urls. The existing test_command tests
  only feed httpx.HTTPStatusError (an Exception), so the
  BaseException-but-not-Exception re-raise that lets CancelledError /
  KeyboardInterrupt propagate was untested in CI. New tests pin both the
  flow-control propagation contract and the both-failure message shape.

* Render dispatch result as a panel and space out the command's output blocks

- Add blank-line separators between each phase of the command (fetch, version
  resolution, image validation, dispatch plan, final result) so the output
  reads as distinct sections rather than one continuous stream.
- Replace the trailing `display_success` + two `display_pair` calls with a
  rich Panel matching the look of `ddev release port-commit`'s completion
  summary. The two run URLs sit inside a cyan-bordered "Workflows dispatched"
  panel with bold-aligned Linux/Windows labels.

* Allow forced test-agent branch fetch

* Add test-agent workflow monitoring

* Fix mypy errors in test-agent module

- dispatch.py: declare DispatchOutcome as a PEP 695 type alias so mypy
  recognises it as a type (the previous TYPE_CHECKING-guarded assignment
  read as a variable on mypy 2.1).
- validation.py: validate both branch/tag invariants up front and assert
  the remaining one is set, removing the trailing app.abort that mypy
  refused to credit as a function terminator.

* Match all patch RCs when resolving --branch in test-agent

* Tighten test-agent types and harden error paths

- Move REPO_OWNER/REPO_NAME from dispatch.py to validation.py so monitoring
  no longer pulls dispatch's async/HTTP module just for two strings.
- Narrow monitor_workflows / monitor_dispatched_workflows first parameter
  from Application to Terminal — matches what the body actually uses and
  what the tests already pass.
- Tighten extract_dispatched_workflows input to tuple[DispatchOutcome,
  DispatchOutcome] so the 2-result contract is explicit at the type level.
- Widen the monitor_dispatched_workflows guard from RuntimeError to
  Exception so httpx network failures abort cleanly with the standard
  "Failed to monitor workflows" message.
- Guard list_tags against non-JSON 2xx responses so registry_errors
  translates them into a clean abort instead of a raw ValueError.

* Make test-agent monitor resilient and quieter

- Tolerate transient httpx errors during polling: keep the prior monitor
  state instead of aborting the entire monitor on one bad request.
- Emit a single final panel in both interactive and non-interactive paths
  so CI logs no longer get flooded with per-poll panels.
- Move DispatchedWorkflow under TYPE_CHECKING in monitoring.py so importing
  monitoring does not eagerly load dispatch and its async machinery.
- Surface the dispatched-run html_urls in the monitor-failed abort message
  so the user can resume monitoring manually.
- Tighten test_dispatch fixtures from list[...] to tuple[...] to match the
  production signature.
- Annotate the FakeAsyncGitHubClient.list_workflow_run_jobs stub with
  AsyncIterator[GitHubResponse[Any]] for consistency.

* Format test_github_async.py with ruff

---------

Co-authored-by: Alexey Pilyugin <alexey.pilyugin@datadoghq.com>
@pull pull Bot locked and limited conversation to collaborators Jul 3, 2026
@pull pull Bot added the ⤵️ pull label Jul 3, 2026
@pull pull Bot merged commit 8bdc2df into ConnectionMaster:master Jul 3, 2026
1 of 3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants