v0.8.34: consent hardening, org images, large ref fixes - #7787
Merged
Conversation
waleedlatif1
commented
Sep 11, 2026
Collaborator
- feat(library): Why No-Code AI Agents Need Live Web Access (And How to Wire It Up) (feat(library): Why No-Code AI Agents Need Live Web Access (And How to Wire It Up) #7765)
- fix(consent): stop asking for consent from a policy we failed to fetch (fix(consent): stop asking for consent from a policy we failed to fetch #7766)
- fix(cli): keep only the agent name from a versioned AI_AGENT declaration (fix(cli): keep only the agent name from a versioned AI_AGENT declaration #7775)
- fix(db): exclude pending-drop columns from inserts (fix(db): exclude pending-drop columns from inserts #7774)
- feat(assistant): support image attachments (feat(assistant): support image attachments #7776)
- fix(slack): read shared app credentials from the environment (fix(slack): read shared app credentials from the environment #7773)
- fix(sidebar): align organization and workspace navigation (fix(sidebar): align organization and workspace navigation #7777)
- feat(library): What to Look for in an AI Workflow Automation Platform: Buyer's Checklist (feat(library): What to Look for in an AI Workflow Automation Platform: Buyer's Checklist #7780)
- fix(provenance): preserve execution files across durable surfaces (fix(provenance): preserve execution files across durable surfaces #7778)
- fix(tools): persist large file outputs before response limits (fix(tools): persist large file outputs before response limits #7781)
- fix(sidebar): simplify organization controls and add logo uploads (fix(sidebar): simplify organization controls and add logo uploads #7783)
- fix(file): clear unused content when writing files (fix(file): clear unused content when writing files #7784)
- fix(knowledge): optimize search and trace pipeline latency (fix(knowledge): optimize search and trace pipeline latency #7782)
- fix(redis): size cold-connection waits to survive a dead handshake (fix(redis): size cold-connection waits to survive a dead handshake #7764)
- fix(sidebar): change organization logos from the icon (fix(sidebar): change organization logos from the icon #7785)
… Wire It Up) (#7765) Co-authored-by: Sim Pi Agent <pi@sim.ai>
#7766) * fix(consent): stop asking for consent from a policy we failed to fetch The consent banner reappears for people who have already answered it. The policy lookup is a cross-origin call to the consent instance, and that origin intermittently answers a bot challenge instead: HTTP 403, an HTML body, and `x-vercel-mitigated: challenge`. A browser fetch cannot solve a JS challenge, so the runtime substitutes a generic opt-in policy and carries on as though the lookup succeeded. Because consent records the fingerprint of the policy they were given under, the substituted policy never matches, the runtime treats it as a material policy change, clears the stored consent, and asks again — with a question the visitor's jurisdiction may not even require. The banner now renders only when the policy was actually resolved. A load that fell back asks nothing; the next load that reaches the real policy asks if it still needs to. The root cause belongs to the consent instance, which should not challenge an endpoint browsers call over XHR. Reproduced against production: consent given, cookie present, then one challenged lookup and the banner returns with the cookie deleted. Also records why the backend URL must stay cross-origin. The documented same-origin rewrite would dodge the challenge, but the instance resolves jurisdiction from the address the request arrives from, and ignores every forwarding header we could send (measured: x-forwarded-for, x-real-ip, true-client-ip, cf-connecting-ip, x-vercel-ip-country). Sim has no edge that supplies a country header, so proxying would resolve every visitor to our own region and stop asking the EU for consent entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tTS2kSemtRPmTq9ezGRCc * fix(consent): keep the explicitly opened dialog working during a fallback The guard suppressed the whole card, including a dialog the visitor opened from the Cookie Policy or the footer control. That leaves a published promise — change your choice at any time — wired to a button that does nothing. Guard the unsolicited banner only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tTS2kSemtRPmTq9ezGRCc --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…ion (#7775) * fix(cli): keep only the agent name from a versioned AI_AGENT declaration * docs(cli): say that usage reports get an approximate location from their address * fix(cli): trim only the versioned AI_AGENT shape to its name
* fix(db): exclude pending-drop columns from inserts * fix(audit): resolve pending-drop insert imports by scope
* feat(assistant): support image attachments * fix(assistant): clean up orphaned image uploads
* fix(slack): read shared app credentials from the environment * fix(slack): retain custom app credential constraints * chore(slack): rename shared app environment module
* fix(sidebar): align organization and workspace navigation * fix(settings): classify recently deleted in workspace move impact * fix(tests): isolate organization layout event subscriptions
…: Buyer's Checklist (#7780) Co-authored-by: Sim Pi Agent <pi@sim.ai>
* fix(tools): persist large file outputs before response limits * fix(tools): cover late attachments and simplify file outputs
* fix(file): clear unused content when writing files * fix(file): preserve validation for malformed content
* fix(knowledge): optimize search and trace pipeline latency * docs: remove standalone search latency notes * fix(knowledge): preserve fallback ranking and measure result bytes
…7764) * fix(redis): size cold-connection waits to survive a dead handshake A dead Redis handshake costs two command deadlines, not one. ioredis sends CLIENT SETNAME/SETINFO on connect and dispatches the INFO ready check only once those settle — on a socket that never answers, they settle by timing out. Only then does the ready check start its own deadline, fail, and tear the socket down for retryStrategy to reconnect. Every readiness wait in the codebase was sized to a single deadline, so it expired while the first attempt was still being diagnosed and a configured retry could never be the thing that rescued it. Introduces `coldConnectionBudgetMs`, which derives a wait from the command deadline and the reconnect delays a caller's own retryStrategy would return, and states the 2x in one place. Both waits now derive from it: - The execution-signal subscriber's readiness budget was the same constant as its commandTimeout (5s and 5s), so ioredis's reconnect was decorative on the cold path that failed in production. It now has a 2s command deadline — this client only issues SUBSCRIBE/UNSUBSCRIBE, always after ready — and a budget with room for two dead attempts and a healthy one. - The shared client's warm-up budget was 10s against a 5s deadline, exactly the moment ioredis would first tear a stalled socket down, so on the case warming exists for it gave up just before recovery could land. Warms the execution-signal subscriber alongside the shared client at process start, in parallel, never throwing: it is the second connection a run opens and was paying its handshake inside its own readiness budget. The shared redis-config mock mirrors the pure budget helper, since consumers now evaluate it at module load and would otherwise fail to import under the global mock. * fix(redis): keep the subscriber's live command deadline at 5s ioredis has one `commandTimeout` for the handshake commands and every live command on the socket, so tightening it to diagnose dead handshakes faster also cut the initial SUBSCRIBE from 5s to 2s — on the one path where a rejection fails the whole run. A ready-but-slow server delays SUBSCRIBE too. Restore the 5s tolerance that subscribe has always had and derive the readiness budget from it: one dead handshake recovers in ~10.5s, so an 11.5s budget still lets ioredis's reconnect rescue the case that failed. The local test mock now delegates to the shared, drift-guarded mirror of the budget arithmetic so the derived number under test is the one production derives. * improvement(redis): warm the signal subscriber on intent, not at worker start Only the tasks that execute a workflow ever subscribe to cancellation signals, and they are a minority of runs: over a week, 71% of Trigger.dev runs never subscribe, and a single document-processing task is 61% on its own. Warming the subscriber in the global init hook opened a TLS connection on every one of those runs that nothing would use. Warm it instead at `executeWorkflowCore` — the one path every execution shares — fire-and-forget as the first statement, so the handshake overlaps the custom-block read and preprocessing ahead of the cancellation subscribe rather than being paid inside that subscribe's readiness budget. No task-id list to keep in sync: coverage follows from the funnel. The subscribe and the warm-up share the hub's memoized readiness wait, so their budgets never compound within a run. The global init keeps warming only the shared client, which nearly every task uses; the Next server keeps warming both at boot, one connection per long-lived process. * chore(instrumentation): import the warm-ups through the path alias The dynamic imports in the Redis warm-up block used relative paths; the file already resolves `@/` and the repository rule is absolute imports. * fix(redis): give every readiness waiter its own deadline and correct the budget model Three corrections from an independent review of the branch. The shared readiness wait carried one timer, so a subscribe that joined an in-flight warm-up inherited only the remainder of that warm-up's budget — down to nothing — where before this branch it was guaranteed a full wait of its own. Waiters now share one ready/end listener pair but each runs its own deadline, and the signal is torn down when its last waiter gives up. The budget model claimed a dead handshake always costs two command deadlines. That is true only without a password: with one, ioredis treats a timed-out AUTH as fatal and tears the socket down after a single deadline, and a connect that never completes is bounded by connectTimeout, which the formula had no term for. `coldConnectionBudgetMs` now charges the largest of those so it holds for either URL shape, and moves to @sim/utils/retry: a pure helper evaluated at module load must not live in a module the test setup replaces wholesale, which had forced the shared mock to carry a verbatim mirror of the arithmetic. The subscriber derives its budget in its constructor from the exact options it is built with; the shared client's reconnect delay is a pure function used by both retryStrategy and the warm-up budget. The Next server no longer opens the signal subscriber at boot: the execution entry point already warms it on intent, and an eagerly opened subscriber that cannot reach Redis would reconnect on a five-second cadence in every idle replica for the life of the process. * fix(redis): leave the readiness wait exactly once per waiter A waiter that timed out was still subscribed to the shared readiness signal, so when that signal later settled for another waiter its cleanup ran a second time. The waiter count drifted negative, the last-waiter teardown could never match again, and the settled signal stayed memoized — a later subscribe during a reconnect would have observed a readiness that had already passed. Each waiter now leaves exactly once, and a settling signal clears itself from the memo so the next waiter observes the connection afresh regardless of the count. * test(redis): assert the fresh subscribe by channel, not by call count A reconnect also re-subscribes surviving channels, so the new subscription is identified by its channel rather than by SUBSCRIBE having been issued once. * test(redis): prove waiter accounting by the teardown a timed-out waiter must perform The settle path clears the memo on its own, so a drifted waiter count only shows when a later lone waiter times out and fails to tear the signal down. Exercise that path directly; the test now fails without the per-waiter guard. * test(execution-core): type the warm-up test's fixtures instead of casting to any The partial fixtures are widened through unknown to the real ExecutionSnapshot and LoggingSession types, as the repository's TypeScript rule prescribes. * fix(redis): connect the signal subscriber on intent and complete the budget model Findings from an independent review of the finished branch. Constructing the execution-signal hub is what connects — ioredis dials in its constructor — so the warm-up that observed that handshake and returned a boolean its only caller discarded is gone. The execution entry point now constructs the hub, synchronously and never throwing, and the subscribe that follows waits on its own budget. With no observer to accommodate, the waiter machinery collapses to a race between the shared readiness signal and a per-waiter deadline, with a per-signal count so the last waiter detaches. The budget's dead-attempt term was still short for an unanswered handshake: after the ready check times out, ioredis half-closes the socket and destroys it only after `disconnectTimeout` when a wedged peer never answers with a FIN. Measured against such a peer: an unauthenticated connection reconnects at 12.5s, past the 11.5s the budget allowed. The term is now `max(connectTimeout, 2 * commandTimeout + disconnectTimeout)`, with the disconnect deadline stated in the shared connection defaults so the budget derives from it. The model is specific to ioredis 5's handshake sequence, so it lives beside the pinned client in `lib/core/config/redis-budget.ts` — a module the global test mock does not replace — rather than in a generic package. Corrects two doc claims: no caller warms the subscriber at server boot, and a late joiner never had a full wait of its own before this branch either — the memoized promise shared its single timer.
Make the organization logo the upload control while preserving the existing permission checks, keyboard access, and upload lifecycle.
Contributor
|
Too many files changed for review (463 files, 100 file limit). Bypass the limit by tagging |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.