Skip to content

[DEV] Teams bots join with a separate passcode — carry it through the real join path and reject unsupported aliases #892

Description

@DmitriyG228

Gap (adjacent to #843/#855, not covered by it)

The #855 intake validation guards native_meeting_id against length (>255) and NUL/control bytes → typed 422. But a value carrying URL-reserved characters (e.g. a Teams passcode accidentally left on the id: 397421056486982?p=X8hcQVTnGNpGelJLSv) passes both guards (short, no control bytes) and is accepted. Then three things break downstream, none typed:

  1. Malformed join URL. construct_meeting_url string-interpolates the raw id into the template (teams: https://teams.microsoft.com/l/meetup-join/{native_meeting_id}), yielding …/l/meetup-join/397421056486982?p=X8hcQVTnGNpGelJLSv — the bot fails to join (join_failure).
  2. Unfindable record. platform_specific_id is stored as the full 397421056486982?p=… string, so the dashboard (which looks up by the clean numeric id 397421056486982) returns 'Meeting not found for platform teams and ID 397421056486982'.
  3. The user sees a bot that 'fails to join' with no indication that the input was malformed.

Reproduced (staging, 2026-07-21)

POST /bots {"platform":"teams","native_meeting_id":"397421056486982?p=X8hcQVTnGNpGelJLSv"} → meeting 13563 created, constructed_meeting_url = https://teams.microsoft.com/l/meetup-join/397421056486982?p=X8hcQVTnGNpGelJLSv, status → failed/join_failure; the same URL passed with a clean native_meeting_id: 397421056486982 + full meeting_url joined fine (meeting 13564, active).

Fix (extend #855 intake validation, at the point of introduction)

At bot-spawn intake, for platforms where native_meeting_id is interpolated as a URL path segment, reject (typed 422) — or parse/normalize — an id containing URL-reserved characters (? & = / # space). Ideally: if the id looks like it carries a query string (?p=…), either extract the passcode into the passcode field or 422 with a message naming the problem ('native_meeting_id must be the bare meeting id; pass the passcode separately or supply meeting_url'). Mirror the #855 pattern: refuse at the door, name the problem, before any record is written.

Acceptance

  • native_meeting_id containing ?/=/&/space → typed 422 at intake (negative control), not a 500/silent join_failure. Delivered — row A2 of the live table in the 2026-08-17 20:42Z comment: meeting_password422 naming passcode, against the pre-fix control on the same stack returning 201 and silently dropping it (the reported bug, reproduced). The fix(meeting-api): reject native_meeting_id carrying URL/query chars at intake (#892) #910 intake guard at bot_spawn/router.py:343 covers ? # & = / and whitespace. ✅
  • A bare id + separate passcode (or a full meeting_url) still works (positive control — meeting 13564 pattern). Delivered — rows A1 and A3, same live table: the bot receives …/meet/397421056486982?p=X8hcQVTnGNpGelJLSv where the pre-fix control gave …/l/meetup-join/555666777888 with no passcode at all; a full meeting_url carrying ?p= is green both ways in. ✅

Not an acceptance leg — a scope note, moved out of the list so it is read as one: the dashboard's own
parser handles this correctly today, which is why the defect only bites raw API and MCP callers.

Found while spawning a Teams witness via the raw API — the dashboard's own parser avoids this, so it only bites API/MCP integrators.

Metadata

Metadata

Assignees

Labels

area: API / Webhooks / MCPAPI, webhooks, MCPgoal: PDG1 dependable captureBusiness goal: dependable meeting capture and lifecycle truthhelp wantedCommunity contribution welcome; scoped but needs some contextstate: awaiting-evaluationChange staged; needs a non-author instrumented validation (D9)type: bugSomething isn't workingwatchedA loop watches this issue; someone who signalled is owed an outcome on delivery

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions