Skip to content

Port channelData optional-subfield fix (#564) to 2.1 (2.1.0-alpha.2) - #567

Merged
Corina (corinagum) merged 2 commits into
release/v2.1from
cg/port-564-to-2-1
Aug 19, 2026
Merged

Port channelData optional-subfield fix (#564) to 2.1 (2.1.0-alpha.2)#567
Corina (corinagum) merged 2 commits into
release/v2.1from
cg/port-564-to-2-1

Conversation

@corinagum

Copy link
Copy Markdown
Contributor

Backports #564 onto the 2.1 preview train and lines the version up for 2.1.0-alpha.2.

What

#564 ("Make channelData subfields optional to avoid throw", fixes #563): the Teams service can send nested channelData objects as empty ({}), which made Pydantic reject the whole activity, so affected bots silently stopped responding. Regression from #504.

Commits

  • Make channelData subfields optional to avoid throw (#564) — clean git cherry-pick -x of 5fcd8fa from main, zero conflicts
  • Preserve alpha sequence after backport mergeversionHeightOffset -12 -> -14

Why the offset moves

release/v2.1 HEAD currently evaluates to 2.1.0-alpha.1, which is already published to PyPI (2.1.0a1, 2026-08-03), so this release has to land on alpha.2.

nbgv height resets only when the version string changes, and versionHeightOffset edits do not reset it. Following the same shape as #544:

Step Height
base release/v2.1 (f469008) 13
+ cherry-pick 14
+ this offset commit 15
+ merge commit 16

So offset = 2 - 16 = -14.

Verified by simulating the merge locally on a branch named release/v2.1 (so it matches publicReleaseRefSpec):

$ nbgv get-version -v SemVer2
2.1.0-alpha.2

Merge instructions

Important

Merge with a merge commit, not squash. The offset above assumes the merge commit exists. A squash merge would put the tip at height 15 and republish alpha.1.

Re-run nbgv get-version -v SemVer2 on release/v2.1 after merging and confirm it prints 2.1.0-alpha.2 before triggering the pipeline. If it is off, note that a corrective offset commit itself adds height, so recompute and re-verify.

Validation

  • pytest packages952 passed
  • pyright0 errors, 0 warnings
  • ruff checkAll checks passed
  • ruff format --check459 files already formatted

Publishing

ADO publish pipeline on release/v2.1, Publish Type = Public. The pipeline detects the - in the version and uses PyPI dist-tag next, so it will not displace latest. Lands on PyPI as 2.1.0a2.

Depends on / pairs with #566 (2.0.16 on the pre-2.1 train).

Corina (corinagum) and others added 2 commits August 19, 2026 11:05
Fixes #563

The Teams service can send nested `channelData` objects as empty objects
( `{}` ). Because `AppInfo.id` was required, Pydantic raised a
`ValidationError`, and since `ActivityTypeAdapter.validate_python` has
no fallback, the entire activity was rejected, so affected bots silently
stopped responding.

Reported against 2.0.15, the first release containing #504. Confirmed to
affect customers, currently in 1:1 chats, with exposure growing as an
upstream server-side rollout ramps.

## Root cause
`channelData.app` is optional, so an absent `app` was always fine, but a
present but empty one wasn't. Auditing the model graph showed this isn't
unique to `app`: 98 sites exist where an optional parent points at a
child with required fields. Seven were reachable from a plain message
activity:
```
channelData.app={}       -> channelData.app.id            Field required
channelData.channel={}   -> channelData.channel.id        Field required
channelData.team={}      -> channelData.team.id           Field required
channelData.tenant={}    -> channelData.tenant.id         Field required
channelData.settings={}  -> channelData.settings.selectedChannel
attachments=[{}]         -> attachments.0.contentType
entities=[{}]            -> entities.0._unknown.type
```

## Change
Make these inbound-only fields optional: `AppInfo.id`, `ChannelInfo.id`,
`TeamInfo.id`, `TenantInfo.id`, `ChannelDataSettings.selected_channel`,
`Attachment.content_type`, `EntityBase.type`.

`MessageUpdateChannelData` and `MessageDeleteChannelData` both inherit
`ChannelData`, so they're covered without separate edits.

`EntityBase.type` is relaxed at the base rather than overridden on
`UnknownEntity`: every concrete entity already overrides `type` with a
`Literal` default, so `UnknownEntity` is the only class inheriting it
raw. This also means an entity type introduced after this SDK version no
longer drops the message.

## Tests
New `test_empty_inbound_objects.py` covering each empty-object case, an
unrecognized entity type, that populated values still parse, and that
absent `channelData` is unchanged.

## Validation
- 852 tests pass across `packages/api` + `packages/apps` 
- `ruff format`  / `ruff check`  clean
- pyright: zero net new errors

## Notes

This is the defensive half. The empty object is a service-side
wire-contract issue and has been raised with that team separately; this
change means the SDK degrades gracefully regardless.

(cherry picked from commit 5fcd8fa)
Target 2.1.0-alpha.2. The backport adds 2 commits plus the merge commit,
putting the merge commit at height 16, so the offset moves -12 -> -14.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@corinagum
Corina (corinagum) merged commit c717e03 into release/v2.1 Aug 19, 2026
5 checks passed
@corinagum
Corina (corinagum) deleted the cg/port-564-to-2-1 branch August 19, 2026 20:14
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