0.1.231
·
2157 commits
to main
since this release
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.