fix(hosting): runner containers leak zombie processes; run them under an init - #5557
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe pull request updates API routing and archive packaging, hardens runner timers and persistence, adds agent chat voice/attachment workflows, introduces drive uploads and local previews, simplifies navigation, migrates table and tracing contracts, and bumps release metadata and dependencies. ChangesPlatform and runtime
Product experience
Shared contracts and package migration
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/oss/src/components/AgentChatSlice/components/clientTools/ConnectToolWidget.tsx (1)
63-108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winThe
phase === "error"branch is unreachable, so manual-retry failures show no reason.
phaseonly becomes"error"viarunConnect(false), which this widget invokes solely from the settled/error chips (Lines 93 and 105) — i.e. only whenmeta.settledis already true. Themeta.settled || outcomebranch at Line 64 therefore always wins, and Lines 98-108 never render: a blocked popup or a failed create surfaces as the generic "Connection not completed" with the actualerrorTextdiscarded.Check the error phase before the settled branch:
🐛 Proposed reordering
+ // ── Error on a manual retry (create failed, popup blocked): show reason + Retry ────────────── + if (phase === "error") { + return ( + <ChipRow icon={<Warning size={13} weight="fill" className="text-colorError" />}> + <Text type="danger" className="!text-xs truncate" title={errorText ?? undefined}> + {errorText ?? "Connection failed."} + </Text> + <RetryButton onClick={() => runConnect(false)} /> + </ChipRow> + ) + } + // ── Settled: the result chip (U1). `outcome` covers the render before `meta.settled` flips. ── if (meta.settled || outcome) { @@ } - // ── Error on a manual retry (create failed, popup blocked): show reason + Retry ────────────── - if (phase === "error") { - return ( - <ChipRow icon={<Warning size={13} weight="fill" className="text-colorError" />}> - <Text type="danger" className="!text-xs truncate" title={errorText ?? undefined}> - {errorText ?? "Connection failed."} - </Text> - <RetryButton onClick={() => runConnect(false)} /> - </ChipRow> - ) - } - // ── Pending: passive marker — the InteractionDock (above the composer) owns the actions ──────
🟡 Minor comments (23)
web/packages/agenta-ui/src/RichChatInput/ComposerSendButton.tsx-35-37 (1)
35-37: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winReplace raw hex colors with supported theme tokens.
Line 37 bypasses the light/dark theme contract with
#191a0dand#b8cb3f; use semantic or supported--ag-color*tokens instead. As per coding guidelines, “Consume theme colors through Ant Design semantic tokens, Tailwind color utilities, or supportedvar(--ag-color*)variables; do not use raw hex colors or--ag-c-*literals.”Source: Coding guidelines
web/packages/agenta-ui/src/RichChatInput/ComposerSendButton.tsx-15-22 (1)
15-22: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCondense the implementation commentary.
These multi-line style and rationale comments exceed the repository’s one-short-line comment convention without documenting a bug, race, or ordering constraint. As per coding guidelines, “Keep in-code comments to at most one short line; use longer comments only for genuinely surprising constraints such as bugs, races, or ordering requirements.”
Also applies to: 32-33
Source: Coding guidelines
web/packages/agenta-ui/src/RichChatInput/RichChatInput.tsx-297-297 (1)
297-297: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDisable submission while dictation is active.
Line 297 makes the editor read-only, but the send control still receives only
disabled. A click can submit a partial transcript while subsequent recognizer updates continue mutating the composer.Proposed fix
- disabled={disabled} + disabled={disabled || dictating}web/oss/src/components/EvaluationRunsTablePOC/components/filters/EvaluationRunsHeaderFilters.tsx-3-3 (1)
3-3: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the active-filter button state when switching implementations.
EvaluationRunsHeaderFiltersstill passesbuttonType={filtersButtonState.buttonType}, butFiltersPopoverTriggerinweb/packages/agenta-ui/src/InfiniteVirtualTable/components/filters/FiltersPopoverTrigger.tsx:8-79always renderstype="default". Active filters will therefore lose their primary-button styling. Make the shared component usetype={buttonType}, or retain the local implementation until the contracts match.web/oss/src/components/Drives/useDriveDrop.ts-47-53 (1)
47-53: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winPending spring timer isn't cleared on unmount. If the drawer/pane unmounts mid-hover (a drop that closes the host, navigation away), the 700ms timeout still fires
onNavigate(path)andsetHoverPath(null)on a dead component.🛡️ Clear the timer on unmount
const clearSpring = useCallback(() => { window.clearTimeout(springTimer.current) springTimer.current = undefined springPath.current = null }, []) + + useEffect(() => () => window.clearTimeout(springTimer.current), [])Also applies to: 89-101
web/oss/src/components/Drives/useMountUpload.ts-88-95 (1)
88-95: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winEmpty
mount.idproduces a malformed request instead of a no-op.?? ""builds/mounts//files/upload, which fails with an opaque server error and surfaces as a generic "Upload failed" item. Bail out and mark the item instead.🛡️ Guard the missing mount id
const controller = new AbortController() controllers.current.set(id, controller) + const mountId = src.target.mount.id + if (!mountId) { + patch(id, {error: "This drive isn't writable"}) + return + } patch(id, {percent: 0, error: null}) uploadMountFile({ - mountId: src.target.mount.id ?? "", + mountId,web/oss/src/components/Drives/driveMedia.ts-167-192 (1)
167-192: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winNormalize
destFolderbefore transmittingpath. The endpoint rejects absolute/mount-escape paths, but this constructs it with onlydestFolder.replace(/\/$/, ""), so leading slashes and./..segments are still sent to the backend instead of being eliminated at the entry point.Source: Coding guidelines
web/oss/src/components/AgentChatSlice/components/ComposerAttachments.tsx-124-140 (1)
124-140: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
role="button"tiles aren't keyboard operable.Both the image tile and
ChiptakeonClickwithrole="button"but notabIndexor key handler, so viewing an attachment is mouse-only. AddtabIndex={0}+ Enter/Space handling when clickable (or wrap in a real<button>).Also applies to: 338-346
web/oss/src/components/AgentChatSlice/components/ComposerAttachments.tsx-99-121 (1)
99-121: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winThe error overlay covers the tile's remove button.
StatusOverlay's error state isabsolute inset-0and rendered after the tile, so a failed attachment can only be retried, not removed. Consider insetting the overlay or re-exposing the remove affordance above it.Also applies to: 405-405
web/oss/src/components/AgentChatSlice/components/AudioPlayer.tsx-28-76 (1)
28-76: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMetadata may already be loaded when the listener attaches.
For a blob/cached
src,loadedmetadatacan fire before this effect runs (preload="metadata"starts loading at mount), leavingdurationat 0 so the total time and progress bar never appear. Run the handler once ifel.readyState >= HAVE_METADATA.🐛 Proposed fix
el.addEventListener("loadedmetadata", onLoadedMetadata) + // Metadata can already be there (blob / cached src loaded before this effect ran). + if (el.readyState >= 1) onLoadedMetadata() return () => {web/oss/src/components/AgentChatSlice/components/ComposerAttachments.tsx-185-196 (1)
185-196: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winObject URLs are recreated on every
fileschange, not just on add/remove.The effect depends on the whole
filesarray, so an uploadstatus/percentupdate revokes and re-creates every URL — audio playback restarts and thumbnails flicker mid-upload. Key the map by uid and only create URLs for uids that don't have one.♻️ Sketch
- useEffect(() => { - const next: Record<string, string> = {} - files.forEach((f) => { - const file = f.originFileObj as File | undefined - const type = file?.type || "" - if (file && (type.startsWith("image/") || type.startsWith("audio/"))) { - next[f.uid] = URL.createObjectURL(file) - } - }) - setPreviews(next) - return () => Object.values(next).forEach((u) => URL.revokeObjectURL(u)) - }, [files]) + // Keyed by uid: only new attachments get a URL, and only removed ones are revoked, so an + // upload progress tick can't reset a playing clip. + const uidKey = files.map((f) => f.uid).join("|") + useEffect(() => { + setPreviews((prev) => { + const next: Record<string, string> = {} + files.forEach((f) => { + const file = f.originFileObj as File | undefined + const type = file?.type || "" + if (!file || !(type.startsWith("image/") || type.startsWith("audio/"))) return + next[f.uid] = prev[f.uid] ?? URL.createObjectURL(file) + }) + Object.entries(prev).forEach(([uid, url]) => { + if (!next[uid]) URL.revokeObjectURL(url) + }) + return next + }) + }, [uidKey])(plus an unmount-only cleanup that revokes whatever remains)
web/oss/src/components/AgentChatSlice/assets/markdown.tsx-195-201 (1)
195-201: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the anchor label for drive links.
driveFileResolver.renderCodecurrently feedstextintoChatFileCode, which resolves a candidate path and rendersDriveFileInlineRefwith the path basename as its visible label. For markdown links, passchildrenthrough, so[report](out/report.csv)displays asreportwhile still resolvingout/report.csv.web/oss/src/components/AgentChatSlice/components/RecordingWaveform.tsx-24-29 (1)
24-29: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
parseRgbsilently degrades to black on non-rgb()computed colors.
getComputedStyle().coloris not guaranteed to serialize asrgb()/rgba()— Chrome returnsoklch(...)/color(srgb 0.2 0.4 0.6)when the authored value uses a modern color space. The digit regex then reads0.2/0.4/0.6as 8-bit channels, so the waveform renders near-black in both themes. UsingglobalAlphawith the raw color string sidesteps the parse entirely:🛡️ Proposed color-space-agnostic tint
- const [r, g, b] = parseRgb(getComputedStyle(canvas).color) - gradient = ctx.createLinearGradient(0, 0, width, 0) - gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.12)`) - gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0.95)`) + // Tint by alpha via globalAlpha so any computed color serialization works. + ctx.fillStyle = getComputedStyle(canvas).color + gradient = null…and in
draw, setctx.globalAlphaper bar from0.12 → 0.95acrossi / (HISTORY - 1)instead of relying on the gradient.Also applies to: 62-65
web/oss/src/components/AgentChatSlice/hooks/useAudioRecorder.ts-138-196 (1)
138-196: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winRe-entrant
start()calls can leak an open mic stream.
recRef.current(the guard at line 139) is only assigned inside thegetUserMedia().then()callback (line 189), so it staysnullfor the whole duration of the pending permission prompt. A secondstart()call issued before the first promise resolves passes the guard, and the second call'sstreamRef.current/recRef.current/audioCtxRef.currentassignments overwrite the first's — the first stream's tracks are then unreachable and never stopped byteardown()(mic stays open), and the orphanedMediaRecorder's cap timer can still fire a strayonCompletelater.🔒 Proposed fix: synchronous re-entrancy guard
+ const startingRef = useRef(false) + const start = useCallback(() => { - if (!supported || recRef.current) return + if (!supported || recRef.current || startingRef.current) return + startingRef.current = true setError(null) cancelledRef.current = false erroredRef.current = false setStatus("requesting") navigator.mediaDevices .getUserMedia({audio: true}) .then((stream) => { + startingRef.current = false if (cancelledRef.current) { stream.getTracks().forEach((t) => t.stop()) return } ... }) .catch((e: unknown) => { + startingRef.current = false teardown() ... }) }, [supported, teardown, meter])docs/design/simplify-nav-new-users/plan.md-145-152 (1)
145-152: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winDo not document a shared literal localStorage key.
"agenta:onboarding:<userId>:nav-simplified-override"is only a placeholder. If copied literally, every account would share one override key. Show the active-user-scoped atom-family implementation used byweb/oss/src/lib/onboarding/atoms.ts.docs/design/simplify-nav-new-users/plan.md-3-6 (1)
3-6: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAlign the Phase 2 scope statements across the design documents. Phase 2 adds the per-user override atom in
web/oss/src/lib/onboarding/atoms.ts, so it does modify a Phase-1 file.
docs/design/simplify-nav-new-users/plan.md#L3-L6: revise the top-level “no Phase-1 file” claim.docs/design/simplify-nav-new-users/README.md#L15-L16: revise the two-phase decision wording.docs/design/simplify-nav-new-users/plan.md#L138-L143: revise the additive-phase description.docs/design/simplify-nav-new-users/status.md#L14-L16: revise the locked decision.docs/design/simplify-nav-new-users/research.md-7-24 (1)
7-24: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRefresh the sidebar line references in both documents. The supplied
useSidebarConfiguses different line locations than those cited, so these references are no longer reliable.
docs/design/simplify-nav-new-users/research.md#L7-L24: update the ownership and target-item references.docs/design/simplify-nav-new-users/plan.md#L98-L115: update the slice implementation references.web/oss/src/components/pages/settings/Organization/General.tsx-229-259 (1)
229-259: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRaw gray utilities won't adapt to dark mode.
bg-gray-100,text-gray-400, andborder-gray-100are fixed light-mode values, so the email chip and option separators will look wrong in dark theme. Prefer semantic tokens (e.g.var(--ant-color-fill-tertiary),var(--ant-color-text-tertiary),var(--ant-color-border-secondary)).As per coding guidelines: "Implement light and dark appearance and interaction states for every added or changed UI element, and verify both themes."
Source: Coding guidelines
web/oss/src/components/pages/settings/FeatureFlags/FeatureFlags.tsx-57-57 (1)
57-57: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winGenerate
text-colorErrorfrom the Ant Design error token.
--ag-colorErroris available, but this class does not resolve through the current Tailwind token/safelist setup; use a generated token class or a supported CSS variable such astext-[color:var(--ag-colorError)]so the icon doesn’t fall back to the parent text color.Source: Coding guidelines
web/oss/src/components/pages/settings/Organization/General.tsx-186-195 (1)
186-195: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winWhitespace-only name passes validation and submits an empty rename.
requiredaccepts" ", andname.trim()then sends an empty string toupdateOrganization.🛠️ Proposed fix
- onFinish={({name}) => renameMutation.mutate({name: name.trim()})} + onFinish={({name}) => renameMutation.mutate({name: name.trim()})} @@ <Form.Item name="name" className="!mb-0 flex-1" - rules={[{required: true, message: "Please enter an organization name"}]} + rules={[ + { + required: true, + whitespace: true, + message: "Please enter an organization name", + }, + ]} >web/oss/src/hooks/usePostAuthRedirect.ts-142-142 (1)
142-142: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPersist the simplified-nav default for invited new users too.
An invited user with
is_new_userreturns at Lines 133-136 before either write, so they retain full navigation. Set this flag before the invited-user early return and remove the duplicate branch writes.The atom contract identifies this as the persisted signup-era setting.
Also applies to: 154-154
web/oss/src/pages/w/[workspace_id]/p/[project_id]/settings/index.tsx-134-134 (1)
134-134: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winReplace the legacy raw color variable.
bg-[var(--ag-c-0517290F)]bypasses the supported semantic theme-token path; use an Ant Design semantic token, Tailwind color utility, or supportedvar(--ag-color*)value instead.As per coding guidelines, “Consume theme colors through Ant Design semantic tokens, Tailwind color utilities, or supported
var(--ag-color*)variables; do not use raw hex colors or--ag-c-*literals.”Source: Coding guidelines
web/oss/src/pages/w/[workspace_id]/p/[project_id]/settings/index.tsx-125-132 (1)
125-132: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMake the organization-ID copy control keyboard accessible.
This Antd
Tagwithouthrefis not keyboard-focusable, andonClickwill not receive activation for keyboard users. Use a<button>for this copy action, or add equivalent keyboard semantics plustabIndex={0}/key handling.
🧹 Nitpick comments (12)
web/oss/src/components/pages/observability/assets/constants.ts (1)
705-765: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse an approved semantic color token.
Replace
var(--ag-zinc-2)with an Ant semantic token or supportedvar(--ag-color*)variable to keep theme colors centrally managed. As per coding guidelines, “Consume theme colors through Ant Design semantic tokens, Tailwind color utilities, or supportedvar(--ag-color*)variables.”Source: Coding guidelines
web/oss/src/services/tracing/types/index.ts (1)
1-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueKeep the new comments to one short line.
Lines 1 and 8 add multi-line commentary where a concise module-level note is sufficient.
As per coding guidelines, “Keep in-code comments to at most one short line.”
Proposed cleanup
-// Span types live in `@agenta/entities/trace`; this module adds only OSS extras. +// Extends shared trace spans with OSS annotations. - import type {TraceSpanNode as EntityTraceSpanNode} from "`@agenta/entities/trace`" - import type {AnnotationDto} from "`@/oss/lib/hooks/useAnnotations/types`" export interface TraceSpanNode extends EntityTraceSpanNode { - /** Attached by the trace/session drawer stores when annotation data is loaded */ annotations?: AnnotationDto[] }Source: Coding guidelines
web/oss/src/components/Drives/DriveExplorer.tsx (1)
1727-1733: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDouble cast hides the synthetic node's shape.
as unknown as MountFilewill silently keep compiling ifMountFilegains required fields, and consumers (recentsByPath, size formatting) then readundefined. Consider building a realMountFilewith explicit defaults, or narrowing the tree builder input to the subset it actually needs ({path, size}).web/oss/src/components/Drives/useMountUpload.ts (1)
73-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the mount invalidation keys source these roots from the shared query-family constants. The current roots match the mount listings, but keeping
queryKeyprefixes next touseMountUpload’s invalidation array prevents future query-family renames from leaving uploads on a stale mount cache.web/oss/src/components/AgentChatSlice/AgentConversation.tsx (1)
1722-1749: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive the enqueue uids from the created
UploadFiles instead of re-deriving the key.Line 1743 re-implements the uid formula from
toUploadFile(Line 1723). If either changes,uploads.enqueuesilently targets uids that don't exist (currently masked because no uploader is wired).♻️ Proposed fix
if (accepted.length) { - setFiles((prev) => [...prev, ...accepted.map(toUploadFile)]) - uploads.enqueue(accepted.map((f) => `${f.name}-${f.lastModified}-${f.size}`)) + const staged = accepted.map(toUploadFile) + setFiles((prev) => [...prev, ...staged]) + uploads.enqueue(staged.map((f) => f.uid)) }web/oss/src/components/AgentChatSlice/components/RecordingBar.tsx (1)
38-48: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueA frame-rate loop for a 1 Hz clock.
The
setSecondsguard keeps React renders at ~1/s, but the rAF callback still runs every frame for the whole take. A ~250 ms interval gives the same visual accuracy at a fraction of the wake-ups:♻️ Proposed simplification
- useEffect(() => { - let raf = 0 - const tick = () => { - const startedAt = startedAtRef.current - const next = startedAt ? Math.floor((Date.now() - startedAt) / 1000) : 0 - setSeconds((prev) => (prev === next ? prev : next)) - raf = requestAnimationFrame(tick) - } - raf = requestAnimationFrame(tick) - return () => cancelAnimationFrame(raf) - }, [startedAtRef]) + useEffect(() => { + const tick = () => { + const startedAt = startedAtRef.current + const next = startedAt ? Math.floor((Date.now() - startedAt) / 1000) : 0 + setSeconds((prev) => (prev === next ? prev : next)) + } + tick() + const id = window.setInterval(tick, 250) + return () => window.clearInterval(id) + }, [startedAtRef])web/oss/src/components/AgentChatSlice/components/VoiceInputButton.tsx (1)
96-104: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueDictation never settles if this button unmounts mid-dictation.
useVoiceInputaborts recognition on unmount, butrecordingnever flips for an unmounting component, soendDictation()/onDictatingChange(false)are skipped and the parent's editor stays locked. A dedicated unmount cleanup closes the gap:🛡️ Proposed cleanup
}, [transcribe.recording, inputRef, onDictatingChange]) + + // Unmounting mid-dictation would otherwise leave the editor locked. + useEffect( + () => () => { + if (wasRecording.current) { + inputRef.current?.endDictation() + onDictatingChange(false) + } + }, + [inputRef, onDictatingChange], + )web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts (2)
36-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLeftover reviewer note in shipped source.
Line 39 carries an unresolved open question addressed to a named individual. Either resolve the bound or move the question to the PR/issue tracker before merge — happy to open an issue for the timeout bound if useful.
251-259: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
setPhase("error")is immediately overwritten on the live-parked path.When
settleParkedCallis true,finish()resetsphaseto"idle", so the"error"phase anderrorTextset just above are never observable. Scope them to the manual-retry path to make the intent explicit:♻️ Proposed clarification
- setPhase("error") - setErrorText(message) - if (settleParkedCall) finish({connected: false, integration, slug, reason: message}) + if (settleParkedCall) { + finish({connected: false, integration, slug, reason: message}) + } else { + setPhase("error") + setErrorText(message) + }web/oss/src/components/AgentChatSlice/hooks/useAttachmentUploads.ts (1)
44-85: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRemoving an attachment leaves its upload running.
Nothing aborts a controller when a file disappears from
files; the request keeps streaming (and itsAbortControllerstays in the map) until unmount, and everypatchfor it is silently dropped. A reconciliation effect keyed on the uid set handles it:♻️ Proposed cleanup on removal
// Abort in-flight uploads on unmount (session switch / pane close). useEffect(() => { const map = controllers.current return () => map.forEach((c) => c.abort()) }, []) + + // A file removed from the tray should not keep uploading. + useEffect(() => { + const live = new Set(files.map((f) => f.uid)) + controllers.current.forEach((controller, uid) => { + if (live.has(uid)) return + controller.abort() + controllers.current.delete(uid) + }) + }, [files])web/oss/src/components/AgentChatSlice/hooks/useVoiceInput.ts (1)
97-109: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueInterim segments joined without a separator.
finalRefsegments are joined with a space, butinterimconcatenates multiple non-finalresult[0].transcriptvalues with no separator, which could glue words together if a browser ever emits more than one interim result in the same event.♻️ Optional consistency fix
- } else { - interim += result[0].transcript - } + } else { + interim += (interim ? " " : "") + result[0].transcript + }docs/design/simplify-nav-new-users/status.md (1)
62-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftAdd executable regression coverage for sidebar visibility.
The status explicitly accepts manual QA only for the five hidden entries and their preserved non-targets. Add coverage in an existing CI-wired web test layer, or wire an OSS test harness, so dependency-array or
isHiddenregressions are caught automatically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 161fe067-38c1-41b2-8990-ffa4a07c0cfc
⛔ Files ignored due to path filters (6)
api/uv.lockis excluded by!**/*.lockclients/python/uv.lockis excluded by!**/*.locksdks/python/uv.lockis excluded by!**/*.lockservices/runner/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlservices/uv.lockis excluded by!**/*.lockweb/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (233)
.gitleaksignoreapi/entrypoints/routers.pyapi/oss/src/core/mounts/service.pyapi/oss/src/middlewares/prefix.pyapi/oss/tests/pytest/unit/middlewares/test_prefix.pyapi/oss/tests/pytest/unit/test_mounts_file_ops.pyapi/pyproject.tomlclients/python/pyproject.tomldocs/design/simplify-nav-new-users/README.mddocs/design/simplify-nav-new-users/context.mddocs/design/simplify-nav-new-users/plan.mddocs/design/simplify-nav-new-users/research.mddocs/design/simplify-nav-new-users/status.mdhosting/docker-compose/ee/docker-compose.dev.ymlhosting/docker-compose/ee/docker-compose.gh.local.ymlhosting/docker-compose/ee/docker-compose.gh.ymlhosting/docker-compose/oss/docker-compose.dev.ymlhosting/docker-compose/oss/docker-compose.gh.local.ymlhosting/docker-compose/oss/docker-compose.gh.ssl.ymlhosting/docker-compose/oss/docker-compose.gh.ymlhosting/kubernetes/helm/Chart.yamlsdks/python/pyproject.tomlservices/pyproject.tomlservices/runner/package.jsonservices/runner/src/engines/sandbox_agent/run-limits.tsservices/runner/src/env.tsservices/runner/src/sessions/persist.tsservices/runner/src/sessions/records-query.tsservices/runner/tests/unit/env-num.test.tsservices/runner/tests/unit/run-limits.test.tsservices/runner/tests/unit/session-persist.test.tsservices/runner/tests/unit/session-records-query.test.tsweb/ee/css-modules.d.tsweb/ee/package.jsonweb/ee/tsconfig.jsonweb/oss/css-modules.d.tsweb/oss/next.config.tsweb/oss/package.jsonweb/oss/src/components/AgentChatSlice/AgentConversation.tsxweb/oss/src/components/AgentChatSlice/assets/attachments.tsweb/oss/src/components/AgentChatSlice/assets/constants.tsweb/oss/src/components/AgentChatSlice/assets/markdown.tsxweb/oss/src/components/AgentChatSlice/components/AgentMessage.tsxweb/oss/src/components/AgentChatSlice/components/AttachmentViewerDrawer.tsxweb/oss/src/components/AgentChatSlice/components/AudioPlayer.tsxweb/oss/src/components/AgentChatSlice/components/ComposerAttachments.tsxweb/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsxweb/oss/src/components/AgentChatSlice/components/Inspector/InspectSessionButton.tsxweb/oss/src/components/AgentChatSlice/components/InteractionDock.tsxweb/oss/src/components/AgentChatSlice/components/MicPermissionNotice.tsxweb/oss/src/components/AgentChatSlice/components/QueuedMessages.tsxweb/oss/src/components/AgentChatSlice/components/RecordingBar.tsxweb/oss/src/components/AgentChatSlice/components/RecordingWaveform.tsxweb/oss/src/components/AgentChatSlice/components/ToolActivity.tsxweb/oss/src/components/AgentChatSlice/components/VoiceInputButton.tsxweb/oss/src/components/AgentChatSlice/components/clientTools/ConnectToolWidget.tsxweb/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.tsweb/oss/src/components/AgentChatSlice/hooks/useAttachmentUploads.tsweb/oss/src/components/AgentChatSlice/hooks/useAudioRecorder.tsweb/oss/src/components/AgentChatSlice/hooks/useVoiceInput.tsweb/oss/src/components/Drives/ContextRail.tsxweb/oss/src/components/Drives/DriveExplorer.tsxweb/oss/src/components/Drives/FilesDrawer.tsxweb/oss/src/components/Drives/SessionFilesDrawer.tsxweb/oss/src/components/Drives/StorageFilesHeader.tsxweb/oss/src/components/Drives/StorageSection.tsxweb/oss/src/components/Drives/VirtualTileGrid.tsxweb/oss/src/components/Drives/configDrive.tsweb/oss/src/components/Drives/driveFileSource.tsxweb/oss/src/components/Drives/driveMedia.tsweb/oss/src/components/Drives/renderers.tsxweb/oss/src/components/Drives/useDriveDrop.tsweb/oss/src/components/Drives/useImagePreviews.tsweb/oss/src/components/Drives/useMountUpload.tsweb/oss/src/components/Drives/useSessionDrive.tsweb/oss/src/components/EvalRunDetails/Table.tsxweb/oss/src/components/EvalRunDetails/atoms/table/run.tsweb/oss/src/components/EvalRunDetails/atoms/traces.tsweb/oss/src/components/EvalRunDetails/components/CompareRunsMenu.tsxweb/oss/src/components/EvalRunDetails/components/EvalTestcaseDrawerAdapter/index.tsxweb/oss/src/components/EvalRunDetails/components/FocusDrawerHeader.tsxweb/oss/src/components/EvalRunDetails/components/FocusDrawerSidePanel.tsxweb/oss/src/components/EvalRunDetails/components/columnVisibility/ColumnVisibilityPopoverContent.tsxweb/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioNavigator.tsxweb/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/index.tsxweb/oss/src/components/EvalRunDetails/evaluationPreviewTableStore.tsweb/oss/src/components/EvalRunDetails/hooks/useCellVisibility.tsweb/oss/src/components/EvalRunDetails/hooks/usePreviewColumns.tsxweb/oss/src/components/EvalRunDetails/utils/buildPreviewColumns.tsxweb/oss/src/components/EvaluationRunsTablePOC/atoms/fetchAutoEvaluationRuns.tsweb/oss/src/components/EvaluationRunsTablePOC/atoms/tableStore.tsweb/oss/src/components/EvaluationRunsTablePOC/components/EvaluationRunsTable/index.tsxweb/oss/src/components/EvaluationRunsTablePOC/components/EvaluationRunsTable/types.tsweb/oss/src/components/EvaluationRunsTablePOC/components/columnVisibility/ColumnVisibilityPopoverContent.tsxweb/oss/src/components/EvaluationRunsTablePOC/components/filters/EvaluationRunsHeaderFilters.tsxweb/oss/src/components/EvaluationRunsTablePOC/hooks/useEvaluationRunsColumns/index.tsxweb/oss/src/components/EvaluationRunsTablePOC/hooks/useEvaluationRunsColumns/utils.tsxweb/oss/src/components/EvaluationRunsTablePOC/hooks/useEvaluatorHeaderReference.tsweb/oss/src/components/EvaluationRunsTablePOC/types.tsweb/oss/src/components/InfiniteVirtualTable/InfiniteVirtualTable.tsxweb/oss/src/components/InfiniteVirtualTable/atoms/columnHiddenKeys.tsweb/oss/src/components/InfiniteVirtualTable/atoms/columnVisibility.tsweb/oss/src/components/InfiniteVirtualTable/atoms/columnWidths.tsweb/oss/src/components/InfiniteVirtualTable/columns/cells.tsxweb/oss/src/components/InfiniteVirtualTable/columns/createStandardColumns.tsxweb/oss/src/components/InfiniteVirtualTable/columns/createTableColumns.tsweb/oss/src/components/InfiniteVirtualTable/columns/types.tsweb/oss/src/components/InfiniteVirtualTable/components/ColumnVisibilityHeader.tsxweb/oss/src/components/InfiniteVirtualTable/components/ColumnVisibilityTrigger.tsxweb/oss/src/components/InfiniteVirtualTable/components/InfiniteVirtualTableInner.tsxweb/oss/src/components/InfiniteVirtualTable/components/TableDescription.tsxweb/oss/src/components/InfiniteVirtualTable/components/TableShell.tsxweb/oss/src/components/InfiniteVirtualTable/components/columnVisibility/ColumnVisibilityMenuTrigger.tsxweb/oss/src/components/InfiniteVirtualTable/components/columnVisibility/ColumnVisibilityPopoverContent.tsxweb/oss/src/components/InfiniteVirtualTable/components/columnVisibility/TableSettingsDropdown.tsxweb/oss/src/components/InfiniteVirtualTable/components/filters/FiltersPopoverTrigger.tsxweb/oss/src/components/InfiniteVirtualTable/context/ColumnVisibilityContext.tsweb/oss/src/components/InfiniteVirtualTable/context/ColumnVisibilityFlagContext.tsxweb/oss/src/components/InfiniteVirtualTable/context/VirtualTableScrollContainerContext.tsweb/oss/src/components/InfiniteVirtualTable/createInfiniteDatasetStore.tsweb/oss/src/components/InfiniteVirtualTable/createInfiniteTableStore.tsweb/oss/src/components/InfiniteVirtualTable/features/InfiniteVirtualTableFeatureShell.tsxweb/oss/src/components/InfiniteVirtualTable/features/index.tsweb/oss/src/components/InfiniteVirtualTable/features/useInfiniteTableFeaturePagination.tsweb/oss/src/components/InfiniteVirtualTable/helpers/createSimpleTableStore.tsweb/oss/src/components/InfiniteVirtualTable/helpers/createTableRowHelpers.tsweb/oss/src/components/InfiniteVirtualTable/helpers/index.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useColumnDomRefs.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useColumnVisibility.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useColumnVisibilityControls.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useContainerResize.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useContainerSize.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useEditableTable.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useExpandableRows.tsxweb/oss/src/components/InfiniteVirtualTable/hooks/useHeaderViewportVisibility.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useInfiniteScroll.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useInfiniteTablePagination.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useResizableColumns.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useRowHeight.tsxweb/oss/src/components/InfiniteVirtualTable/hooks/useScopedColumnVisibility.tsxweb/oss/src/components/InfiniteVirtualTable/hooks/useScrollConfig.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useScrollContainer.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useSmartResizableColumns.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useTableActions.tsxweb/oss/src/components/InfiniteVirtualTable/hooks/useTableExport.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useTableHeaderHeight.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useTableKeyboardShortcuts.tsweb/oss/src/components/InfiniteVirtualTable/hooks/useTableManager.tsxweb/oss/src/components/InfiniteVirtualTable/hooks/useTableRowSelection.tsweb/oss/src/components/InfiniteVirtualTable/index.tsweb/oss/src/components/InfiniteVirtualTable/providers/ColumnVisibilityProvider.tsxweb/oss/src/components/InfiniteVirtualTable/providers/InfiniteVirtualTableStoreProvider.tsxweb/oss/src/components/InfiniteVirtualTable/types.tsweb/oss/src/components/InfiniteVirtualTable/utils/columnUtils.tsweb/oss/src/components/Playground/Components/TestsetDropdown/TestsetPreviewPanelWrapper.tsxweb/oss/src/components/SessionInspector/PanelSessionInspectorButton.tsxweb/oss/src/components/SharedDrawers/AddToTestsetDrawer/components/PreviewSection.tsxweb/oss/src/components/SharedDrawers/SessionDrawer/components/SessionDrawerButton/index.tsxweb/oss/src/components/SharedDrawers/SessionDrawer/store/sessionDrawerStore.tsweb/oss/src/components/SharedDrawers/SessionDrawer/types.tsweb/oss/src/components/SharedDrawers/TraceDrawer/components/TraceContent/components/OverviewTabItem/index.tsxweb/oss/src/components/SharedDrawers/TraceDrawer/components/TraceTree/assets/spanVisibility.tsweb/oss/src/components/SharedDrawers/TraceDrawer/components/TraceTree/index.tsxweb/oss/src/components/SharedDrawers/TraceDrawer/store/traceDrawerStore.tsweb/oss/src/components/Sidebar/dynamic/registry.tsweb/oss/src/components/Sidebar/hooks/useSidebarConfig/index.tsxweb/oss/src/components/Sidebar/scopes/settingsScope.tsxweb/oss/src/components/TestcasesTableNew/components/TestcaseHeader.tsxweb/oss/src/components/TestcasesTableNew/components/TestcasesTableShell.tsxweb/oss/src/components/TestcasesTableNew/index.tsxweb/oss/src/components/TestcasesTableNew/state/rowHeight.tsweb/oss/src/components/TestsetsTable/TestsetsTable.tsxweb/oss/src/components/TestsetsTable/assets/createTestsetsColumns.tsxweb/oss/src/components/TestsetsTable/atoms/fetchTestsets.tsweb/oss/src/components/TestsetsTable/components/TestsetsHeaderFilters.tsxweb/oss/src/components/pages/agent-home/OnboardingEntry.tsxweb/oss/src/components/pages/agent-home/hooks/useCreateAgent.tsweb/oss/src/components/pages/agents/AgentsPage.tsxweb/oss/src/components/pages/agents/store.tsweb/oss/src/components/pages/observability/assets/constants.tsweb/oss/src/components/pages/observability/assets/exportUtils.tsweb/oss/src/components/pages/observability/assets/getObservabilityColumns.tsxweb/oss/src/components/pages/observability/components/AvatarTreeContent.tsxweb/oss/src/components/pages/observability/components/NodeNameCell.tsxweb/oss/src/components/pages/observability/components/SessionsTable/assets/getSessionColumns.tsxweb/oss/src/components/pages/observability/components/StatusRenderer.tsxweb/oss/src/components/pages/observability/components/TimestampCell.tsxweb/oss/src/components/pages/prompts/PromptsPage.tsxweb/oss/src/components/pages/settings/FeatureFlags/FeatureFlags.tsxweb/oss/src/components/pages/settings/Organization/General.tsxweb/oss/src/components/pages/settings/assets/navigation.test.tsweb/oss/src/components/pages/settings/assets/navigation.tsweb/oss/src/hooks/usePostAuthRedirect.tsweb/oss/src/lib/helpers/dynamicEnv.tsweb/oss/src/lib/hooks/usePreviewEvaluations/index.tsweb/oss/src/lib/hooks/usePreviewEvaluations/types.tsweb/oss/src/lib/onboarding/atoms.tsweb/oss/src/lib/onboarding/index.tsweb/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/[app_id]/index.tsxweb/oss/src/pages/w/[workspace_id]/p/[project_id]/settings/index.tsxweb/oss/src/services/tracing/types/index.tsweb/oss/src/state/app/atoms/fetcher.tsweb/oss/src/state/entities/shared/README.mdweb/oss/src/state/entities/shared/createPaginatedEntityStore.tsweb/oss/src/state/entities/testcase/paginatedStore.tsweb/oss/src/state/entities/testset/paginatedStore.tsweb/oss/src/state/newObservability/atoms/queries.tsweb/oss/src/state/onboarding/selectors.tsweb/oss/src/state/settings/featureFlags.tsweb/oss/tsconfig.jsonweb/package.jsonweb/packages/agenta-api-client/package.jsonweb/packages/agenta-entities/package.jsonweb/packages/agenta-entities/src/evaluationRun/core/schema.tsweb/packages/agenta-entities/src/evaluationRun/etl/resolveMappings.tsweb/packages/agenta-entities/src/trace/etl/exportMatchingTraces.tsweb/packages/agenta-entities/src/workflow/index.tsweb/packages/agenta-entities/src/workflow/state/index.tsweb/packages/agenta-entities/src/workflow/state/inspectMeta.tsweb/packages/agenta-entities/tests/unit/export-matching-traces.test.tsweb/packages/agenta-entity-ui/package.jsonweb/packages/agenta-playground/src/index.tsweb/packages/agenta-playground/src/state/execution/index.tsweb/packages/agenta-playground/src/state/index.tsweb/packages/agenta-shared/package.jsonweb/packages/agenta-ui/src/InfiniteVirtualTable/index.tsweb/packages/agenta-ui/src/InfiniteVirtualTable/types.tsweb/packages/agenta-ui/src/RichChatInput/ComposerSendButton.tsxweb/packages/agenta-ui/src/RichChatInput/RichChatInput.tsxweb/packages/agenta-ui/src/RichChatInput/index.tsweb/packages/agenta-ui/src/RichChatInput/plugins/SendButton.tsxweb/packages/agenta-ui/src/RichChatInput/plugins/dictation.tsweb/tests/package.json
💤 Files with no reviewable changes (57)
- web/oss/src/components/InfiniteVirtualTable/components/ColumnVisibilityHeader.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useColumnVisibilityControls.ts
- web/oss/src/components/InfiniteVirtualTable/atoms/columnHiddenKeys.ts
- web/oss/src/components/InfiniteVirtualTable/components/TableDescription.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useScrollContainer.ts
- web/oss/src/components/InfiniteVirtualTable/features/useInfiniteTableFeaturePagination.ts
- web/oss/src/components/InfiniteVirtualTable/providers/InfiniteVirtualTableStoreProvider.tsx
- web/oss/src/components/SharedDrawers/SessionDrawer/types.ts
- web/oss/src/components/InfiniteVirtualTable/atoms/columnWidths.ts
- web/oss/src/components/InfiniteVirtualTable/components/columnVisibility/ColumnVisibilityMenuTrigger.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useColumnDomRefs.ts
- web/oss/src/components/InfiniteVirtualTable/components/ColumnVisibilityTrigger.tsx
- web/oss/src/components/InfiniteVirtualTable/columns/createTableColumns.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useTableActions.tsx
- web/oss/src/components/InfiniteVirtualTable/features/index.ts
- web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionDrawerButton/index.tsx
- web/oss/src/components/InfiniteVirtualTable/components/columnVisibility/TableSettingsDropdown.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useContainerResize.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useContainerSize.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useTableKeyboardShortcuts.ts
- web/oss/src/components/InfiniteVirtualTable/helpers/index.ts
- web/oss/src/components/InfiniteVirtualTable/context/ColumnVisibilityContext.ts
- web/oss/src/components/InfiniteVirtualTable/context/VirtualTableScrollContainerContext.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useRowHeight.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useScopedColumnVisibility.tsx
- web/oss/src/components/InfiniteVirtualTable/columns/types.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useTableRowSelection.ts
- web/oss/src/components/InfiniteVirtualTable/providers/ColumnVisibilityProvider.tsx
- web/oss/src/components/InfiniteVirtualTable/createInfiniteTableStore.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useTableManager.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useScrollConfig.ts
- web/oss/src/components/InfiniteVirtualTable/atoms/columnVisibility.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useColumnVisibility.ts
- web/oss/src/components/InfiniteVirtualTable/columns/createStandardColumns.tsx
- web/oss/src/components/InfiniteVirtualTable/InfiniteVirtualTable.tsx
- web/oss/src/components/InfiniteVirtualTable/types.ts
- web/oss/src/components/InfiniteVirtualTable/helpers/createSimpleTableStore.ts
- web/oss/src/components/InfiniteVirtualTable/utils/columnUtils.ts
- web/oss/src/components/InfiniteVirtualTable/components/filters/FiltersPopoverTrigger.tsx
- web/oss/src/components/InfiniteVirtualTable/context/ColumnVisibilityFlagContext.tsx
- web/oss/src/components/InfiniteVirtualTable/helpers/createTableRowHelpers.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useInfiniteTablePagination.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useSmartResizableColumns.ts
- web/oss/src/components/InfiniteVirtualTable/components/InfiniteVirtualTableInner.tsx
- web/oss/src/components/InfiniteVirtualTable/createInfiniteDatasetStore.ts
- web/oss/src/components/InfiniteVirtualTable/features/InfiniteVirtualTableFeatureShell.tsx
- web/oss/src/components/InfiniteVirtualTable/columns/cells.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useHeaderViewportVisibility.ts
- web/oss/src/components/InfiniteVirtualTable/components/TableShell.tsx
- web/oss/src/components/InfiniteVirtualTable/components/columnVisibility/ColumnVisibilityPopoverContent.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useInfiniteScroll.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useTableHeaderHeight.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useResizableColumns.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useEditableTable.ts
- web/oss/src/components/InfiniteVirtualTable/hooks/useExpandableRows.tsx
- web/oss/src/components/InfiniteVirtualTable/hooks/useTableExport.ts
- web/oss/src/components/InfiniteVirtualTable/index.ts
| const local = useLocalFile(path) | ||
| const mountRes = useMountFileMediaSrc(mount, path) | ||
| // A local blob can still fail to decode (corrupt / unsupported) — surface it like a mount error | ||
| // so the viewer shows its "couldn't load" card rather than a broken element. | ||
| const [localFailed, setLocalFailed] = useState(false) | ||
| if (local) { | ||
| return { | ||
| src: localFailed ? null : local.objectUrl, | ||
| isPending: false, | ||
| failed: localFailed, | ||
| onError: () => setLocalFailed(true), | ||
| } | ||
| } | ||
| return mountRes | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Per-path state isn't reset when path/local changes. These hooks are called from long-lived body components (ImageBody, TextBody, …) that keep their instance across path prop changes, so:
useDriveFileText: switching from local file A to B keepstext= A's content and reportsisPending: false, so the viewer renders the wrong body until B resolves.useDriveMediaSrc: alocalFailedset for A sticks, so B renders the "couldn't load" card immediately.
🐛 Reset the per-file state when the source changes
- const [localFailed, setLocalFailed] = useState(false)
+ const [failedUrl, setFailedUrl] = useState<string | null>(null)
+ const localFailed = local != null && failedUrl === local.objectUrl
if (local) {
return {
src: localFailed ? null : local.objectUrl,
isPending: false,
failed: localFailed,
- onError: () => setLocalFailed(true),
+ onError: () => setFailedUrl(local.objectUrl),
}
} useEffect(() => {
+ setText(undefined)
if (!local) return
let cancelled = falseAlso applies to: 70-86
Railway Preview Environment
Updated at 2026-07-29T20:08:36.244Z |
Context
On a long-running dev deployment, one runner container had accumulated 3,010 zombie processes after 4 days of uptime; two other runner containers on the same box had 292 and 79. Zombies hold PID/task slots, so a busy runner eventually exhausts process limits.
The root cause is that the runner (a Node/tsx server) runs as PID 1 inside its container. Node reaps its own direct children, but harness runs spawn process trees, and when an intermediate process exits, its orphaned children re-parent to PID 1. Node never reaps those, so every orphaned grandchild stays a zombie forever. The compose files set
init: truefor other services (worker-streams, worker-queues, cron, the gh api), but the runner block never got it.Changes
Adds
init: trueto therunnerservice in every compose file that defines it:oss/docker-compose.dev.yml,ee/docker-compose.dev.ymloss/docker-compose.gh.yml,ee/docker-compose.gh.ymloss/docker-compose.gh.local.yml,ee/docker-compose.gh.local.ymloss/docker-compose.gh.ssl.ymlWith the flag, Docker starts
docker-init(tini) as PID 1. It reaps orphaned children and forwards signals; the runner process becomes its child. Same pattern the other services in these files already use, placed in the same# === EXECUTION === #section.This is a runtime flag only. No image rebuild is needed; it takes effect the next time the container is recreated.
Tests
docker compose -f <file> configfor all 7 files: each renders the runner service withinit: true.docker run --init --rm --entrypoint cat <runner-image> /proc/1/commprintsdocker-init.--initshows the container command itself as PID 1 (no reaper).ee/docker-compose.dev.harness.local.yml(a local-only overlay that only patches runner env/volumes) is untouched; it inheritsinitfrom the base file.What to QA
run.sh ... --recreate runner) and run a few agent executions.ps aux | grep defunctinside the runner container should stay near zero instead of growing with every run.https://claude.ai/code/session_01BhfwcD7KTfPE1FZpdCqJ19