Skip to content

Python: Preserve empty hosted MCP allowlists - #29

Closed
eavanvalkenburg wants to merge 30 commits into
mainfrom
hosted-mcp-allowlist-assessment
Closed

eavanvalkenburg wants to merge 30 commits into
mainfrom
hosted-mcp-allowlist-assessment

Conversation

@eavanvalkenburg

Copy link
Copy Markdown
Owner

Motivation & Context

Preserve explicitly supplied empty allowed_tools lists when constructing hosted MCP tool configurations.

Description & Review Guide

  • What are the major changes? Update the OpenAI and Foundry MCP helpers to distinguish None from an empty list, and document the distinction.
  • What is the impact of these changes? Empty lists are retained in serialized requests. Omitted and nonempty lists, approval settings, and authentication configuration retain their existing behavior.
  • What do you want reviewers to focus on? Regression coverage for construction and SDK request serialization, including Foundry URL and project-connection configurations.

Related Issue

N/A.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after a language prefix) — a workflow keeps the label and title prefix in sync automatically.

eavanvalkenburg and others added 30 commits September 18, 2026 10:36
…osoft#8505)

* Python: Restrict Hyperlight to Python 3.14

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Refresh development dependencies

Consolidate the open Python dependency updates, remove pandas from the shared development environment, and update checker compatibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Keep pandas sample-local

Allow sample type checkers to validate the PEP 723 script without requiring pandas in the shared development environment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Gate Hyperlight backend below 3.15

Keep the Hyperlight connector compatible with the workspace Python range while installing the native Wasm backend only where wheels are published.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Python: Gate Psycopg binary below 3.15

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…8432)

* Bind always approval responses to surfaced requests

* Addrses PR comments
…icrosoft#8512)

* Added pycharm ignore entry to .gitignore

* Added hooks for name and id for OTEL

* Added project ID for trace surfacing + inner response id preserved for traces

* restored .gitignore

* clarified name to project_arm_id and moved method to match code base patterns

* Restore uv.lock after branch merge

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 5641780c-318a-4def-8692-710b2b061c60

* Fix Foundry attribution setup and agent response ownership

Separate per-agent project identity from exporter configuration, preserve export on optional discovery failures, and capture response identity from the owned chat result. Consolidate invocation telemetry state and document both setup paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: bfc650ea-638e-4c20-aae6-f63cca98462e

* Simplified the ADR

* Remove redundant Azure Monitor dependency test

Keep the tracing dependency and documentation fix without testing behavior owned by Azure Monitor.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Clarify required PromptAgent version in tracing sample

Document FOUNDRY_AGENT_VERSION as required for PromptAgents and optional for HostedAgents, preserving runtime behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Simplify Foundry tracing README guidance

Keep the package README focused on installation, the setup helper and the sample link. Retain sample prerequisites and run instructions without repeating the telemetry investigation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5641780c-318a-4def-8692-710b2b061c60
Copilot-Session: bfc650ea-638e-4c20-aae6-f63cca98462e
Wait for the inner stream to block before signalling shutdown or cancellation, independently of the retained preemption deadline. Drain pending test tasks and verify that the blocked call is cancelled before cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…microsoft#8509)

* Python: preserve expression results in SendActivity output

Emit evaluated expressions as data while retaining authored-template interpolation. Add regression coverage for string and mapping activity forms.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: preserve expression results in SendActivity output

Emit evaluated expressions as data while retaining authored-template interpolation. Add regression coverage for string and mapping activity forms.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit e52b5c7)

* Python: document and test SendActivity output migration

Cover authored templates and explicit expressions in both activity forms, and document the intentional single-pass expression output contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: keep SendActivity guidance with the executor

Move the concise migration examples into the class documentation and remove the detailed package README section.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Python: avoid repeated workflow reference traversal

Walk shared workflow mappings and lists once by identity, preserve environment-reference discovery, and reject ancestor cycles with a definition error. Add deterministic regressions and document the discovery contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: avoid repeated workflow reference traversal

Walk shared workflow mappings and lists once by identity, preserve environment-reference discovery, and reject ancestor cycles with a definition error. Add deterministic regressions and document the discovery contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: type the shared workflow definition test fixture

Declare the heterogeneous workflow fixture as dict[str, Any] so all test type checkers can validate its nested immutability assertions without changing runtime behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: keep workflow discovery details in API docstrings

Remove the detailed traversal section from the package README. Keep traversal limitations with the helper and document cycle errors on workflow creation methods.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ft#8508)

* Python: evaluate autoSend expressions before emitting output

Evaluate configured autoSend values against workflow state in function-tool and agent actions. Preserve output bindings, defaults, and resume behavior, and cover automatic output with local regression tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: evaluate autoSend expressions before emitting output

Evaluate configured autoSend values against workflow state in function-tool and agent actions. Preserve output bindings, defaults, and resume behavior, and cover automatic output with local regression tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: refresh autoSend decisions when resuming actions

Defer tool output expression evaluation until execution is approved, and reevaluate agent output expressions on resumed turns. Cover changed-state and evaluator-error cases without changing stored output bindings.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: keep autoSend details in executor docstrings

Remove the granular automatic-output section from the package README and document the behavior alongside the executor classes and approval handler.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix: preserve dynamic function middleware

Keep harness callbacks consistent as tool collections change.
Preserve existing function wrappers and request-local composition
without coupling reusable options to agent instances.

* fix: compose harness middleware across clients

Keep callback composition independent of client service discovery.
Use request-local execution scopes without changing reusable options.
Cover scope restoration and tool repair after collection updates.

* fix: retain middleware through options clones

Keep harness callback composition consistent across function-loop
iterations by retaining the full chain on function wrappers.
Cover real client-driven clones, repeated calls and approval flows.

* test: extend dynamic tool middleware coverage

Cover Python harness callback consistency across progressive tool
loading, continuation changes, failures, approvals and reused options.

* test: simplify streaming response collection

* fix: preserve harness pipeline boundaries

Build callback chains around factory results instead of sharing them
between active client calls. Track active invocation callbacks across
opaque function decorators while preserving explicit continuations.

Cover replacement factories, captured clients and failure cleanup.

* docs(dotnet): explain function callback flow

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…oft#8372)

* Python: Add request-scoped Foundry agent factories

* Python: Refine Foundry factory ownership and session persistence

Validate functional response batches before state changes, persist ordinary Invocations factory sessions, and document fresh execution objects as the factory author responsibility. Stabilize recovery fixtures and correct CI test typing.

* fix: align workflow continuation checks

Require recorded completion for both Invocations workflow types.
Preserve stored state when incomplete continuation is rejected.

* refactor: simplify hosting checkpoint handling

Keep checkpoint error policy with the workflow runtime.
Add HTTP coverage for request-scoped factory behavior.

* Python: Simplify request-scoped Foundry agents

* Python: Fix ResponseStream test typing

* Python: Align request agent validation and stream cleanup

* Python: Manage request-scoped Invocations agents

* Python: Validate request agent callable shape

* Python: Address request agent review feedback

* docs: Clarify request-scoped Foundry agent creation
…FICC (microsoft#8531)

* Pass ChatClientAgent tools per run only rather than setting on FICC

* Fix test errors
…s (incl. per-session GitHubToken) (microsoft#5735)

Built on top of upstream's v1.0.0 migration (microsoft#6381). This change:

- Bumps SDK from 1.0.0 to 1.0.1 (no breaking changes in usage)

- Forwards all newly-added SessionConfig properties through CopyResumeSessionConfig (CopySessionConfig already uses source.Clone() so it's fine). Most importantly GitHubToken (per-session GitHub identity for multitenant scenarios), plus ClientName, ModelCapabilities, OnElicitationRequest, OnEvent, EnableConfigDiscovery, IncludeSubAgentStreamingEvents, DefaultAgent, Agent, Commands, CreateSessionFsProvider, and ~25 more

- Makes sessionConfig non-nullable on the SessionConfig constructor (SDK requires SessionConfig.OnPermissionRequest, so a null SessionConfig was a runtime trap)

- Makes onPermissionRequest a required (non-nullable) parameter on the tools/instructions constructor and the corresponding AsAIAgent extension overload, positioned before optional params

- Removes the fallback 'new SessionConfig { Streaming = true }' path in RunCoreStreamingAsync since _sessionConfig is now non-nullable

- Updates unit tests, integration tests, and sample for the new required-param signatures

Co-authored-by: Sai Kothuri <saik@microsoft.com>
Co-authored-by: Roger Barreto <19890735+RogerBarreto@users.noreply.github.com>
…#8507)

* Improve regex validation for shell and file access

* Address PR comments

* Fix build errors

* Address PR comment
…soft#7902) (microsoft#8547)

* Python: Include InvokeAzureAgent input.arguments in agent text

Fold evaluated input.arguments into the text passed to agent.run() so
argument-only InvokeAzureAgent actions no longer silently invoke with
an empty string.

* Python: skip implicit fallback when InvokeAzureAgent arguments are present

Addresses review on microsoft#7907: arguments-only actions no longer append
System.LastMessage / Workflow.Inputs fallback text to their structured
fields in chained workflows. Adds a regression test with poisoned
fallback state.
…8560)

* Require expected keys in tool argument evaluation

* Test missing and explicit-null tool arguments
…handoff (microsoft#8551)

clean_conversation_for_handoff dropped every non-text content type to avoid
replaying runtime-only tool-control payloads, but that also discarded genuine
user input such as images, files, and vector store references, so agents
receiving a handoff lost the user's multimodal context.

Keep semantic multimodal content (data, uri, hosted_file, hosted_vector_store)
on user messages while keeping assistant and other non-user messages text-only,
since providers treat multimodal parts as input-only and reject them when
replayed on assistant turns. Tool-control payloads (function_call,
function_result, approval payloads) remain excluded as before.

Fixes microsoft#7822
…e default branch (microsoft#8490)

* Python: surface switch-case condition errors instead of routing to default

SwitchCaseEdgeGroup wrapped every case predicate in a bare `except Exception`
that only logged a warning, so a predicate that raised was treated exactly like
one that returned False: the message fell through to the default branch and the
workflow completed successfully. A broken routing predicate became silently
wrong routing rather than a visible failure.

This contradicted `Edge.should_route`, which documents that predicate errors are
deliberately allowed to surface "to avoid masking logic bugs", and made the
existing `FanOutEdgeRunner` error path (span status EXCEPTION, re-raise)
unreachable for switch-case groups, so the failures were missing from telemetry
too. The block was marked `# pragma: no cover`.

Also align `FanOutEdgeRunner` with `SingleEdgeRunner`, which checks
`_can_handle` before evaluating a condition: drop messages no target can handle
before running the selection function. Without this, removing the catch would
turn an undeliverable message into a raised AttributeError instead of a
type-mismatch drop.

Fixes microsoft#8489

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

* Python: drop target-mismatched messages before the fan-out selection function

The runner fans every message out to all of the source's edge runners and gathers
them with `gather_cancelling_siblings_on_error`. `FanOutEdgeRunner` checked
`message.target_id` only after running the selection function, so a message
addressed to an executor reached through a different edge still had this group's
selection function evaluated against it - and a raise there cancelled the sibling
runner that was actually delivering the message.

Check `message.target_id` against `_target_map` first and return
DROPPED_TARGET_MISMATCH, ahead of the `_can_handle` check, matching
`SingleEdgeRunner`, which checks the target and then `_can_handle` before
evaluating `Edge.should_route`.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…icrosoft#8464)

* Python: preserve AgentResponse metadata in WorkflowAgent streaming

* Python: preserve agent ID in response aggregation

---------

Co-authored-by: WhaleTech <304937387+ryo-whaletech@users.noreply.github.com>
* Python: bound PowerFx state construction

Validate structural budgets before declarative state copies and PowerFx symbol conversion. Preserve copy isolation and temporary bindings, reject budget failures explicitly, and add focused boundary regressions. Fixed limits and the new Core hook require compatibility and dependency-floor review before release.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: bound PowerFx state construction

Validate structural budgets before declarative state copies and PowerFx symbol conversion. Preserve copy isolation and temporary bindings, reject budget failures explicitly, and add focused boundary regressions. Fixed limits and the new Core hook require compatibility and dependency-floor review before release.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Python: keep PowerFx limit documentation near implementation

Remove the detailed package README section and document per-traversal counting next to the budget implementation. Preserve the limits, runtime behavior, and method error documentation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Python: Fail closed on unresolved agent-tool approvals

* Python: Model workflow approval as child return

* Python: Route workflow delegation with structured output

* Python: Reject shared approval state collisions

* Python: Isolate delegated approval continuation state

* Python: Isolate custom child approval state

* Python: Track delegated approval ownership

* Python: Isolate parent approval middleware state
prepend_agent_framework_to_user_agent looked up the exact key
"User-Agent" when deciding whether an incoming headers dict already
carries a user agent. HTTP header names are case-insensitive, so a caller
passing default_headers={"user-agent": "my-app/1.0"} (for example to
OpenAI clients or the A2A agent) ended up with two User-Agent entries:
their original one plus a new agent-framework one. httpx then sends both
header lines, and which value a server honors is undefined.

Find the existing entry case-insensitively and prepend to it in place, so
exactly one User-Agent header survives.

Co-authored-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
)

encode_checkpoint_value used isinstance checks for dict and list, so dict
subclasses (defaultdict, Counter, OrderedDict) nested in checkpoint state
were silently flattened to plain dicts on save. The save-time validation
passed because decoding still succeeded, but restored workflows got a
plain dict: a defaultdict access pattern like state["missing"].append(x)
then raises KeyError on resume, and Counter/OrderedDict types are lost.

Only plain dict/list values now take the JSON path; subclasses are pickled
like tuples and sets already were, preserving type and behavior through
the round trip. collections.Counter joins OrderedDict, defaultdict, and
deque in the built-in restricted-unpickler allowlist so the common stdlib
dict subclasses restore under restricted decoding too.

Co-authored-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
A2AAgent._create_timeout_config only accepted float and httpx.Timeout, so
A2AAgent(url=..., timeout=30) raised TypeError. Per PEP 484's numeric tower
an int is a valid float, and httpx.Timeout itself accepts ints. Accept int
(and float) while continuing to reject bool and other types.

Co-authored-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com>
…h finish reasons (microsoft#8478)

* Python: mark hosted /responses incomplete on content_filter and length finish reasons

The hosted /responses path iterated each AgentResponseUpdate's contents but never read
its finish_reason, so a turn the model stopped early (Azure OpenAI content filter, token
limit) ended with status "completed" and no trace of the cut-off. Callers could only
detect a filtered turn by matching the canned refusal text.

_OutputItemTracker now records truncating finish reasons from both the plain-agent and
workflow update loops, and _handle_response ends the response with response.incomplete
plus incomplete_details.reason ("content_filter" / "max_output_tokens") instead of
response.completed, mirroring the OpenAI Responses shape. The refusal text is still
delivered as an output item.

Fixes microsoft#8475

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

* Persist the incomplete reason across resilient checkpoint recovery

Recovery rebuilds _OutputItemTracker from the persisted response, so a marker held only in
memory was lost if the crash landed between a filtered update and a later one. Mirror it
into the stream's internal_metadata (persisted with every checkpoint, like the last
checkpoint id) and restore it in the tracker constructor.

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

* Handle updates through one tracker entry point and pair snapshots with the checkpoint an update follows

_OutputItemTracker.handle_update records the update's finish reason and then handles its
contents, so the plain-agent and workflow loops share one ordering rule.

The workflow loop snapshotted the response against the latest checkpoint in storage at the
time an update was consumed. _SignalledIterator drives the workflow one update ahead, so by then
the runner could already have checkpointed past the update; a crash after that snapshot resumed
the workflow with the update never replayed and its output (and any incomplete reason) lost.
_SignalledIterator now takes an optional stamp coroutine that the driver awaits right after each
item is produced, before advancing the wrapped iterator again, and the loop pairs snapshots with
that stamped checkpoint id. After the workflow completes, its final checkpoint is paired with the
full output so recovery does not replay the last superstep. The real-crash recovery integration
tests that were xfailed against microsoft#7809 pass with this.

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

* Fix pre-commit and typing failures

- Drop the redundant `None, None` Generator parameters flagged by pyupgrade.
- Type the `finish_reason` test parameters as `FinishReasonLiteral` instead of
  `str` + `type: ignore`, which `ty` does not honour.
- Read the text delta with `.get()` so the `ResponseStreamEvent` TypedDict
  union does not need narrowing.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…osoft#8201) (microsoft#8284)

* Python: add public TypedDict for AgentExecutor checkpoint state (microsoft#8201)

Expose AgentExecutorCheckpointState / AgentSessionCheckpointState, validate restore field types, and cover partial/malformed/forward-compatible payloads.

* fix(checkpoint): address microsoft#8284 review on session TypedDict ownership (#1)

- Define AgentSessionDict on AgentSession (to_dict return) so the shape is
  not duplicated in AgentExecutor; keep AgentSessionCheckpointState as alias.
- Include ServiceSessionId mapping in service_session_id.
- Type on_checkpoint_restore as AgentExecutorCheckpointState only.
- Sync root stub exports (__init__.pyi) with runtime __all__.

* fix(checkpoint): validate AgentExecutor restore element types (microsoft#8284)

Enforce Message/Content element types, string pending-request keys, and
required agent_session.session_id so malformed checkpoints fail at restore.

* fix(checkpoint): validate AgentSessionDict fields on restore

- Validate agent_session.state / service_session_id types
- Raise WorkflowCheckpointException when session restore fails
- Drop unshipped AgentSessionCheckpointState alias; use AgentSessionDict

* fix(checkpoint): keep session/executor checkpoint payloads as dict

TypedDict schemas remain public for documentation and validation, but to_dict and on_checkpoint_save/restore stay dict[str, Any] so subclasses, samples, and pyright stay compatible.

* fix(checkpoint): keep AgentSessionDict optional keys under postponed annotations

Use a required base plus total=False subclass, stop exporting AgentSessionDict from the public package root, and tighten AgentExecutor restore validation with walrus checks.

---------

Co-authored-by: minelhi <3417378192@qq.com>
Co-authored-by: LI <2484593937@qq.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
…ft#8542)

* feat(core): add session-scoped mode to FileAccessProvider

Add an opt-in session_scoped mode (with an optional explicit scope) that
confines file-access tool operations to a working folder derived from the
session id via the shared _storage_key_segment derivation under the
file-access-specific "~access-" prefix, mirroring FileMemoryProvider. The
mode fails closed (ValueError) when neither a scope nor a session id is
available, creates the session folder in before_run, appends a
session-isolation note to the tool instructions, and logs the derived
folder at debug level. All eight tools route store calls through a single
_session_path helper; ls/grep treat an empty directory as the session root
and grep results stay session-relative. Default mode is byte-for-byte
unchanged.

Adds seven regression tests, including the COLLIDING_IDENTIFIERS attack
strings parametrization, fail-closed coverage, scope override, and
shared-store default regression. All harness file-access tests pass; the
only full-suite failure is the pre-existing
test_feature_stage.py::test_feature_id_allows_lowercase_values on main.

Addresses microsoft#8539.

* feat(core): wire file_access_session_scoped through create_harness_agent

Expose the FileAccessProvider session-scoped mode from
create_harness_agent via a new file_access_session_scoped flag (default
False, preserving shared-store semantics), threaded through
_assemble_context_providers and synchronized across the .pyi stub and the
package AGENTS.md file-access section.

Adds a pass-through regression test. Addresses microsoft#8539.

* docs(core): describe scoped file access isolation relative to the workspace

The session-scoped instruction suffix claimed files are never visible to
other sessions, which is wrong when an explicit scope intentionally shares
a workspace across sessions. Rephrase the suffix, the class docstring, and
the session_scoped parameter docs to describe isolation relative to the
resolved workspace (session or configured scope).

Addresses review feedback on PR microsoft#8542.

* fix(core): harden session-scoped file access boundaries

Address review feedback on PR microsoft#8542:
- Normalize ls/grep directories with _normalize_relative_path(...,
  is_directory=True) in scoped mode so a ".." directory cannot escape the
  session workspace through a permissive custom store.
- Place every scoped working folder under a fixed ~access- namespace
  directory, so file access and file memory working folders cannot collide
  when both providers share one store root and a literal-safe identifier.
- Treat a non-empty explicit scope as enabling scoped mode by itself,
  aligning with FileMemoryProvider(scope=...).

Adds parent-traversal, namespace-collision, and scope-only regression
tests and updates layout assertions to the namespaced folder shape. All
harness suites pass; ruff clean.

This branch had an error being deployed

1 failed and 1 inactive deployments
github-app-auth — c0afb6c3 Deployed Sep 21, 2026 by eavanvalkenburg via team_check #40
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.