Skip to content

0.1.231

Choose a tag to compare

@github-actions github-actions released this 18 Aug 17:02
· 2157 commits to main since this release
96ecc13
fix: prevent stale dashboard context in Ask (#3047)

* fix(desktop): recover child sessions after cookie loss

* fix(desktop): match identity gate auth sizing

* docs(desktop): document shared workspace sign-in

* fix: harden feedback dispatch and desktop sessions

* fix(desktop): reload adopted child sessions

* fix(factory): guard grouped feedback dispatch

* Tighten desktop chat-first spacing

* docs: gate ship on a current base

A worktree can be created from a stale ref with its local main ref equally
stale, so the branch reports itself current from the inside. Four live Codex
worktrees measured 1,144 commits behind origin/main this way.

* docs: make answered feedback clarifications re-enter the sweep

Asking the reporter a question put our own reply in the thread, which the
start-cursor rule read as an ownership marker, so every thread we asked about
became invisible on later runs and the answers were never read.

* feat(core): page owners when an app's chat stops answering

scripts/chat-health.mjs --strict already measured this correctly and exited 1
on a partial outage, but nothing scheduled it and nothing paged. An app
answering a minority of its turns was found by a user posting in Slack.

Runs the same turn-scoring on the durable recurring-jobs sweep, beside the
stale reap that is already there for the same reason: it is the only durable
driver on serverless. Scoped to the app it runs in, so no cross-app database
credential has to exist.

Threshold is 50% over at least 5 turns in an hour, well above chat-health's
0.1 review budget -- that number answers 'is this degraded', this one answers
'is chat down'. One page per outage, not one per sweep.

insufficient-data, cooldown, and check-failed are each their own outcome. A
monitor that could not read the ledger has not found the app healthy.

* fix(core): stop OpenAI rejecting every tool schema that contains a union

OpenAI's function-calling validator answers a `oneOf` anywhere in a function
schema with:

  400 Invalid schema for function 'mutate-dashboard':
  In context=('properties','operations','type','0','items'), 'oneOf' is not permitted

That 400 rejects the WHOLE request -- every tool in the payload, not just the
offending action -- before a single token streams. Zod v4's toJSONSchema emits
`oneOf` for every z.discriminatedUnion, so one action with a union broke all
chat in the app that shipped it.

Production: 178,442 events, 786 users, first seen 2026-07-01, still firing.

It stayed hidden for seven weeks because the gateway folded the provider's 400
into a generic 500 with an opaque ERROR ID, and those ids do not resolve through
the org event-id lookup -- the real message only surfaces by searching the
project by tag.

Fixed at the boundary that already exists for this exact class: OpenAI rejects
`propertyNames` too, and stripUnsupportedSchemaKeywords was already stripping
it on every conversion path. anyOf accepts the same documents for a
discriminated union, and the tool-input validator already narrows both keywords.
Every action in every template is covered, not just the one that was reported.

Also: the chat-health pager coerced an unreadable cooldown stamp into 'never
paged', which would page every sweep while settings stayed unreadable.

* chore: publish branch work in .changeset/fresh-chat-restore-warning.md, packages/core, templates/analytics, … (11 files)

* fix: document best-effort Ask context cleanup

* fix: harden stale dashboard cleanup

* fix: align desktop update behavior and Clips test

* Hide Builder from custom provider keys

* fix: guard analytics context across route transitions

* fix: scope analytics selection cleanup to tab

* fix: preserve legacy analytics state cleanup

* fix: tighten chat-first app controls

* fix: keep workspace app menus on one line

* fix: close analytics route cleanup races

* fix: localize compact chat-first app labels

* fix: bound Clips recovery cleanup

* fix: bound Clips recovery state cleanup

* test: cover bounded Clips cleanup

* fix: harden action schemas and mail oauth coverage

* fix(core): type every tool-schema position OpenAI requires typed

After the oneOf fix, OpenAI's validator reached the next layer and rejected:

  400 Invalid schema for function 'mutate-dashboard':
  In context=(...,'anyOf','3','properties','value'), schema must have a 'type' key

Zod emits a bare {} for z.unknown()/z.any(). OpenAI 400s the whole request for
any schema position without a type, so one such field breaks every chat in the
app -- identical blast radius to oneOf, one layer deeper.

137 z.unknown()/z.any() sites across templates, so this belongs at the same
boundary as the oneOf and propertyNames rewrites, not in each action.

The replacement is a union of concrete JSON types. That shape is proven against
the live validator: setFilterDefault.value, a z.union of primitives in the same
tool schema, was never flagged while the typeless sibling beside it was.
additionalProperties: true is a boolean rather than a subschema so it needs no
type; array items get one bounded level instead of recursing.

Verified against the real mutate-dashboard shape: no oneOf, no propertyNames,
and every subschema position typed or composed.

* chore(analytics): document the selection-probe coercion opt-out

guard:no-silent-coercion flagged the empty catch in readSelectedDashboardObject.
Unreadable and unselected render the same UI here and throwing would break route
transitions, so this is the documented opt-out rather than a behaviour change --
recorded so a reviewer sees the decision instead of a silent pass.