Skip to content

fix(test): resolve shell/cwd unit-test failures surfaced by GitHub-hosted runners (LAC-2386)#33

Merged
lacymorrow merged 569 commits into
LAC-2385/blacksmith-to-github-runnersfrom
LAC-2386/fix-shell-cwd-tests
Jul 9, 2026
Merged

fix(test): resolve shell/cwd unit-test failures surfaced by GitHub-hosted runners (LAC-2386)#33
lacymorrow merged 569 commits into
LAC-2385/blacksmith-to-github-runnersfrom
LAC-2386/fix-shell-cwd-tests

Conversation

@lacymorrow

Copy link
Copy Markdown
Owner

Summary

Fixes the 19 unit (linux) failures that surfaced on PR #28 when CI moved to GitHub-hosted runners (run 27512032937). These were real pre-existing failures, invisible because Blacksmith runs on dev had been cancelled for weeks.

Stacked on #28 (LAC-2385/blacksmith-to-github-runners) so the unit jobs here actually run on GitHub-hosted runners. Merging this turns #28 green, which then merges to dev.

Root causes (three, all environment-sensitive)

  1. getCwd() degraded to process.cwd() when the ambient (AsyncLocalStorage) instance context wasn't entered. Only CLI bootstrap() enters it; Effect-based callers run with InstanceRef instead. On CI the process cwd is the runner checkout dir, so shell permission patterns and cwd resolution pointed at the wrong directory — the permission ask hook never fired (requests.length 0 vs 1). Fix: shell tool / session prompt / system prompt pass the instance directory as an explicit fallback, and the test fixture now enters the ambient instance context the way production bootstrap() does.

  2. InstanceRef lost in lash session tests. Upstream 0c9cfe9 (refactor(instance): remove legacy runtime fallback) removed the Instance.current fallback from attach(), so tests calling AppRuntime.runPromise from plain async code died with InstanceRef not provided. New test/fixture/app.ts#runTestApp provides InstanceRef explicitly — upstream's intended pattern (Effect.provideService(InstanceRef, ctx)).

  3. cwd-sentinel cleanup ate real output. The shell wrapper echoes a __LASH_CWD__ sentinel after each command. Cleanup truncated everything from the start of the sentinel's line, which (a) dropped unterminated stdout sharing that line (printf out), and (b) dropped stderr chunks that arrive after the sentinel in the merged stdout+stderr stream. stripSentinel() now splices out only the sentinel line.

Also resets the shell-mode cwd singleton between tmpdir fixtures so a cd in one test can't leak into the next.

Verification

  • bun test test/session/prompt*.test.ts test/tool/shell.test.ts test/plugin/cwd.test.ts — 95 pass / 0 fail (was 19 fail in CI, reproduced locally)
  • bun run typecheck clean
  • Full package suite run locally; unit (linux) on this PR is the authoritative gate

Out of scope

unit (windows) has 112 pre-existing failures — separate follow-up issue (LAC-2386 acceptance is linux green).

Closes LAC-2386.

opencode-agent Bot and others added 30 commits June 2, 2026 19:05
Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
rekram1-node and others added 24 commits June 7, 2026 12:37
…odule projects (anomalyco#28761)

Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
LAC-2281: Lash Upstream Sync — 2026-06-01 (256 commits)
Rebased on top of dev after PR #26 (LAC-2281, 2026-06-01 sync) merged.
Many original conflicts auto-resolved because dev already contained 256
of the 303 upstream commits. Remaining conflicts resolved per
UPSTREAM_SYNC.md playbook with all lash invariants preserved:

- agent_cycle = shift+tab in TUI keybind config
- EventCwdUpdated in SDK event union (incl. GlobalEvent.payload)
- getCwd()/setCwd() in shell-mode + prompt.ts cwd sentinel
- Double-left-border row layout in prompt/index.tsx
- ExecutionModeProvider + WorkingDirProvider wrap <App /> in app.tsx
- tabs.tsx removeSessions navigate() hoisted out of produce callback
  (mirrors c04019d pattern for titlebar.tsx)

Post-merge typecheck fixes included:
- which() import path (filesystem services consolidation)
- @/, @shell-mode, @tui-integration aliases in tui + cli tsconfigs
  (after upstream TUI extraction 106f8e9)
- *.wasm module declaration
- ModelID via ModelV2.ID after upstream provider/index.ts removal

Refs: LAC-2337
LAC-2337: Lash Upstream Sync — 2026-06-08 (303 commits)
* fix(app): mount shortcuts per titlebar tab (anomalyco#33567)

* fix(tui): normalize autocomplete attachments

* refactor(core): drop filesystem entry mime

* fix(app): use fixed titlebar tab widths (anomalyco#33572)

* refactor(schema): extract shared public schemas (anomalyco#33571)

* chore: update nix node_modules hashes

* fix(app): throttle directory tree loading (anomalyco#33576)

* refactor(core): remove schema forwarding facades (anomalyco#33577)

* fix(app): create scoped drafts from home (anomalyco#33578)

* feat(cli): add API request command

* fix(tui): sort model picker by release date (anomalyco#33558)

* chore: generate

* fix(app): clear viewed session notifications (anomalyco#33574)

* fix(app): route new sessions through tabs (anomalyco#33582)

* fix(mcp): bind oauth callback to IPv4 loopback (anomalyco#30022)

* fix(opencode): restore v1 account config (anomalyco#33590)

Co-authored-by: Test <test@opencode.test>

* refactor(app): simplify layout hierarchy (anomalyco#33588)

* fix(mcp): restore legacy tool names (anomalyco#33593)

* fix(skill): emit base directory as filesystem path, not file:// URL (anomalyco#33580)

Co-authored-by: Aiden Cline <aidenpcline@gmail.com>

* chore: generate

* fix(mcp): prevent resource key collisions (anomalyco#33596)

* feat(app): update all components to use v2 tokens (anomalyco#33598)

* chore: generate

* deps: update OpenTUI to 0.4.2 (anomalyco#33610)

* chore: update nix node_modules hashes

* fix(app): make session navigation stable and fast (anomalyco#33569)

Co-authored-by: Brendan Allan <git@brendonovich.dev>

* chore: generate

* fix(app): share synced session data (anomalyco#33624)

* refactor(app): rename cached context accessors (anomalyco#33627)

* ci: notify the current Discord alert role

* feat(console): add support actions (anomalyco#33492)

* feat(tui): add diff viewer keybind (anomalyco#33365)

* chore: generate

* fix(app): always apply safe area insets (anomalyco#33619)

* feat(tui): add main branch source to diff mode (anomalyco#30942)

* fix(enterprise): use delete for remove share

* tui: fix multi-day duration formatting (anomalyco#33651)

Report elapsed days and remaining hours instead of showing zero days and
the total duration as hours.

Close anomalyco#32957
Close anomalyco#32956

* docs(llm): propose AI library design (anomalyco#33666)

* chore: generate

* refactor(ui): isolate session components (anomalyco#33670)

* chore: update nix node_modules hashes

* zen: migrate to new inference

* fix(stats): strip locale prefixes from data routes

* refactor(stats): simplify locale prefix stripping

* feat(mcp): append server instructions to context (anomalyco#32490)

Co-authored-by: Aiden Cline <aidenpcline@gmail.com>

* chore: generate

* fix(mcp): hide denied resource template tool (anomalyco#33686)

* refactor(core): simplify model requests

* feat(core): generate model variants

* chore: generate

* zen: track model tier in stats

* fix: restore dev CI (anomalyco#33701)

* tui: remove logo animation (anomalyco#33633)

Render branding as a static logo to avoid continuous redraws and mouse-driven visual effects.

* sync release versions for v1.17.10

* refactor(schema): extract public event definitions (anomalyco#33579)

* chore: generate

* chore: update nix node_modules hashes

* docs: add GMI Cloud provider entry to providers directory (anomalyco#32914)

* fix(opencode): always print MCP OAuth URL (anomalyco#33716)

* fix(core): scope fff broad scanning

* fix(core): disable fff broad scanning

* fix(core): handle unavailable fff index

* fix(core): preserve unconfigured console models

* fix(core): lower OpenAI text verbosity

* feat(core): add session snapshot and revert system (anomalyco#33226)

* chore: generate

* fix(app): remove session loading stripe (anomalyco#33649)

Co-authored-by: Test <test@opencode.test>

* refactor(protocol): extract server contracts (anomalyco#33708)

* refactor(app): centralize session state (anomalyco#33641)

* chore: generate

* fix(app): clear late session notifications (anomalyco#33753)

Co-authored-by: Test <test@opencode.test>

* chore: update nix node_modules hashes

* feat(sdk): add HttpApi clients and embedded host (anomalyco#33445)

* chore: generate

* chore: update nix node_modules hashes

* fix(app): separate provider lifetimes and reactive ownership (anomalyco#33739)

* chore: generate

* refactor(app): lift parent session navigation (anomalyco#33782)

* chore: generate

* feat(app): draggable tabs (anomalyco#31364)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>

* chore: generate

* feat(app): ui improvements (anomalyco#32438)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>

* chore: generate

* fix(app): close missing session tabs (anomalyco#33785)

* fix(app): preserve todo dock across sessions (anomalyco#33778)

* fix(app): remove produce default values in server session state (anomalyco#33793)

* fix(app): update all v1 new-session icons (anomalyco#32017)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>

* fix(storybook): support prompt state capture (anomalyco#33798)

* feat(app): no sesssions empty state (anomalyco#33315)

* chore: generate

* feat(app): restyle v2 jump-to-latest button (anomalyco#33809)

* fix(app): improve home action availability (anomalyco#33805)

* fix(app): restore tab layout consistency (anomalyco#33804)

* fix(app): reveal tab avatar on hover (anomalyco#33824)

Co-authored-by: Test <test@opencode.test>

* feat(app): add Chrome tab cycle shortcuts (anomalyco#33838)

* Revert "deps: update OpenTUI to 0.4.2 (anomalyco#33610)" (anomalyco#33842)

* fix(app): use project server for prompt drafts (anomalyco#33850)

* chore: update nix node_modules hashes

* fix(client): exclude generated files from formatting (anomalyco#33832)

Co-authored-by: Test <test@opencode.test>

* feat(ui): publish ui package (anomalyco#33681)

* sync release versions for v1.17.11

* fix(app): hide mobile titlebar setting in production (anomalyco#33894)

* chore: update nix node_modules hashes

* feat(server): add permission request endpoints (anomalyco#33774)

* chore: generate

* feat(app): improve titlebar tab layout (anomalyco#33914)

* feat(stats): add i18n support (anomalyco#33693)

* fix(app): suspend while recent models load (anomalyco#33921)

* chore: generate

* docs(schema): document package conventions (anomalyco#33768)

* fix(app): restore new layout shortcuts (anomalyco#33929)

* refactor(schema): normalize module patterns (anomalyco#33770)

* chore: generate

* zen: migrate to new inference

* zen: new inference

* fix(app): adjust home search and header layout (anomalyco#33923)

* fix(tui): load root sessions in session switcher (anomalyco#33931)

Request root sessions before applying the session list limit so child
sessions cannot crowd roots out of the switcher.

Keep the synchronized cache available while requests are pending or fail,
reconcile results with live updates, and retain current and pinned sessions.
Preserve selection by session ID when asynchronous results reorder the list.

Closes anomalyco#16270
Closes anomalyco#32725

* fix(app): persist home project selection (anomalyco#33935)

* chore: generate

* refactor(schema): tighten public contracts (anomalyco#33771)

* chore: generate

* refactor(schema): isolate v1 contracts (anomalyco#33769)

* feat(app): group prompt projects by server (anomalyco#33941)

* feat(sdk): restore session runtime operations (anomalyco#33777)

* chore: generate

* refactor(core): support tiered layer nodes (anomalyco#33937)

* chore: generate

* fix(app): use tab-scoped servers in sessions (anomalyco#33946)

* fix(app): enable composer shortcuts on drafts (anomalyco#33956)

* refactor(app): split session composer (anomalyco#33954)

* chore: generate

* fix(mcp): scope auth status to server URL (anomalyco#33924)

* chore: generate

* feat(llm): pass strict through tool definitions for Codex parity (anomalyco#33392)

* fix(app): simplify question prompt (anomalyco#33968)

Co-authored-by: Test <test@opencode.test>

* feat(core): split MCP timeout configuration (anomalyco#33977)

Co-authored-by: Test <test@opencode.test>

* fix(core): refine small model defaults (anomalyco#33926)

* tweak: hide MCP access token prefix (anomalyco#33711)

* fix(app): enable auto-accept in session settings (anomalyco#33974)

* fix(app): suspend hidden terminal renderer (anomalyco#33990)

Co-authored-by: Test <test@opencode.test>

* fix(ui): normalize tooltip trigger layout (anomalyco#33979)

Co-authored-by: Test <test@opencode.test>
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>

* refactor(opencode): stop legacy v2 event emission (anomalyco#33993)

* fix(sdk): wake embedded session execution (anomalyco#33992)

* feat(sdk): expose active sessions (anomalyco#33991)

* chore: generate

* fix(app): bump ghostty-web to prevent terminal resize hangs (anomalyco#34020)

Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>

* chore: update nix node_modules hashes

* refactor(app): use dropdown for project selector (anomalyco#33984)

* refactor(app): replace tab drag handling with dndkit (anomalyco#33880)

Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>

* chore: generate

* feat(oauth): unify OAuth callback browser pages (anomalyco#34025)

* chore: update nix node_modules hashes

* feat(app): refine session UI styling (anomalyco#33860)

Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>

* chore: generate

* core: prevent UI imports compiling TSX source

* fix(tui): preserve renderer initialization errors (anomalyco#33996)

Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>

* fix(opencode): preserve skill resource paths (anomalyco#34052)

* fix(core): refresh cached remote skills (anomalyco#34059)

* fix(acp): surface prompt errors (anomalyco#34061)

* feat(app): updates to design system (anomalyco#34066)

* fix(acp): skip resume transcript replay (anomalyco#34070)

* fix(mcp): refresh credentials on reauthentication (anomalyco#33717)

* tweak: update OAuth callback branding (anomalyco#34081)

* fix(llm): omit stateless response item ids (anomalyco#34027)

* fix(core): enlarge OAuth callback card (anomalyco#34082)

* feat(app): add new session workspace controls (anomalyco#34085)

* fix(app): consolidate add project action (anomalyco#34086)

* fix(acp): enrich permission prompts (anomalyco#34079)

* docs(providers): document blacklist and whitelist model filtering (anomalyco#33792)

* fix(app): wait for persisted home state before reading collapsed state (anomalyco#34088)

* fix(acp): send partial completed tool updates (anomalyco#34091)

* fix(data): canonicalize locale pages

* docs: route enterprise contact links (anomalyco#34080)

* fix(data): clarify unchanged rank

* feat(stats): add clickable section headings (anomalyco#34095)

* chore: generate

* fix(core): resolve prompt attachment mime types

* chore: generate

* fix(core): authorize external read paths

* fix(llm): end reasoning before responses

* chore: generate

* fix(app): animate prompt selectors after loading (anomalyco#34101)

* feat(api): add finite durable session history pages (anomalyco#34097)

* chore: generate

* fix(app): slow tooltip display for models (anomalyco#30745)

Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com>

* chore: update nix node_modules hashes

* feat(sdk): expose live event stream (anomalyco#34098)

* chore: generate

* fix(app): centralize notification state (anomalyco#34105)

* zen: new inference

* fix: apply lash-specific patches after upstream merge

- Fix SolidJS accessor usage in question-dock (sdk → sdk())
- Remove dead session-prompt-dock.tsx (no imports, replaced by composer)
- Update bun.lock

* fix(tui): use generated event union (anomalyco#34118)

Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>

* fix(app): keep bare slash as plain inline code (anomalyco#34122)

Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>

* fix(app): hide home session archive action (anomalyco#34136)

Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>

* refactor(core): separate out location node functionality and integrate into v2 (anomalyco#34119)

* chore: generate

* fix(mcp): surface OAuth completion errors (anomalyco#34145)

* fix(mcp): request refresh token scope (anomalyco#34125)

* chore: update nix node_modules hashes

* fix(app): migrate composer tooltips to v2 (anomalyco#34147)

Co-authored-by: Jay V <air@live.ca>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>

* feat(core): port v2 runtime fixes onto dev

Cherry-picks the packages/core changes from the v2 branch onto dev:
- combined ordered stdout/stderr in AppProcess + bash structured output
- edit/apply-patch return FileDiff info with status and line stats
- ignore no-op model switches; record reasoning timestamps
- return unexpected local tool defects to the model and continue
- keep OAuth account metadata out of request bodies
- nest OpenAI reasoning effort/summary options
- load OpenCode provider config asynchronously; batch plugin boot
- export latest public event manifest

Includes the supporting schema reasoning time field and regenerated
client/SDK types.

* chore: generate

* fix(opencode): allow empty provider config default (anomalyco#34167)

Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>

* chore: update nix node_modules hashes

* fix(app): space home sessions from scrollbar (anomalyco#34132)

Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>

* fix(ui): make select hover feedback immediate (anomalyco#34121)

Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>

* feat(app): update home screen alignment + markdown styles (anomalyco#34172)

* fix(desktop): recognize normal auth metadata input prompts in connect provider dialog (anomalyco#33024)

* fix(app): reconcile session pages with concurrent events (anomalyco#34042)

* chore: generate

* fix(app): batch new session tab navigation (anomalyco#34196)

* feat(app): minor visual updates (anomalyco#34205)

* refactor(core): make node build bind maps conditionally (anomalyco#34218)

* feat(app): new debug bar (anomalyco#34237)

* chore: generate

* fix(tui): register `prompt.skills` keybinds (anomalyco#34180)

* refactor(core): rename app node modules (anomalyco#34238)

* chore: generate

* test(core): cover app node builder graphs (anomalyco#34244)

* refactor(core): move session test to nodes (anomalyco#34245)

* chore: generate

* refactor(core): move more tests to nodes (anomalyco#34248)

* chore: generate

* fix(app): transition draft project updates (anomalyco#34252)

* fix(sdk): preserve V2Event name for SSE streams (anomalyco#34171)

* chore: update nix node_modules hashes

* feat(client): generate complete protocol client (anomalyco#34164)

* fix(app): disable `add project` when given server is offline (anomalyco#34294)

* fix(desktop): avoid destroyed window permission checks (anomalyco#34300)

* zen: new inference

* zen: update alert role

* fix(mcp): reconnect after OAuth even when server is disabled

Closes anomalyco#33915

* fix(mcp): clarify debug oauth probe (anomalyco#34350)

* feat(desktop): Display stored totals for Tokens and Cost in Desktop Session Context (anomalyco#28887)

* chore: generate

* fix(app): wrap model.set in startTransition (anomalyco#34351)

* chore: generate

* feat(tui): integrate ServerAuth headers into transport configuration for external served TUI thread (anomalyco#29876)

* refactor(core): refine layer node replacements (anomalyco#34377)

* chore: generate

* test(core): fix layer node replacement type expectation (anomalyco#34386)

Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>

* fix(app): disable empty server chevron (anomalyco#34292)

* zen: budget

* zen: budget

* feat(app): sticky session list header (anomalyco#34220)

Co-authored-by: Brendan Allan <git@brendonovich.dev>

* feat(app): show loader on session hover (anomalyco#34224)

* chore: generate

* fix(tui): use V2Event after upstream SSE name preservation (anomalyco#34171)

Upstream renamed the SSE event union back to `V2Event` (no `V2Event1`
suffix). Drop the alias import.

LAC-2490

---------

Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com>
Co-authored-by: Dax Raad <d@ironbay.co>
Co-authored-by: Kit Langton <kit.langton@gmail.com>
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
Co-authored-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: Test <test@opencode.test>
Co-authored-by: Chris Yuan <honglong@gmail.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
Co-authored-by: vimtor <vn4varro@gmail.com>
Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com>
Co-authored-by: Frank <frank@anoma.ly>
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
Co-authored-by: 4rcadia <97033226+Arcadi4@users.noreply.github.com>
Co-authored-by: opencode <opencode@sst.dev>
Co-authored-by: Isaac Huang <isaac.h@gmicloud.ai>
Co-authored-by: Dax <mail@thdxr.com>
Co-authored-by: James Long <longster@gmail.com>
Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com>
Co-authored-by: Ariane Emory <97994360+ariane-emory@users.noreply.github.com>
Co-authored-by: Stefan Avram <98915060+Slickstef11@users.noreply.github.com>
Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com>
Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com>
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
Co-authored-by: Jay V <air@live.ca>
Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com>
Co-authored-by: Ben Guthrie <benjee.012@gmail.com>
Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com>
Co-authored-by: Max Anderson <max.a.anderson95@gmail.com>
…sted runners (LAC-2386)

Three root causes, all environment-sensitive rather than runner-specific:

1. getCwd() silently degraded to process.cwd() when the ambient instance
   context was not entered. Effect-based callers (shell tool, session
   prompt, system prompt) now pass the instance directory as an explicit
   fallback, and the test fixture enters the ambient instance context the
   same way production bootstrap() does. This fixes the shell permission
   tests (permission ask hook never fired because the command resolved
   against the runner checkout dir instead of the test tmpdir) and the
   cwd.test.ts suite.

2. Upstream 0c9cfe9 removed the legacy Instance.current fallback from
   attach(), so lash tests calling AppRuntime.runPromise from plain async
   code lost InstanceRef. New test/fixture/app.ts runTestApp() provides
   InstanceRef explicitly (upstream's intended pattern) for
   prompt-missing-file, prompt-special-chars, and prompt-variant tests.

3. The cwd sentinel cleanup truncated everything after the sentinel, but
   stderr chunks from the merged output stream can arrive after the
   sentinel is written to stdout. stripSentinel() now splices out only
   the sentinel line, preserving late stderr (fixes 'shell captures
   stdout and stderr'). It also cuts at the sentinel itself rather than
   the start of its line so unterminated stdout sharing the sentinel's
   line survives (fixes 'shell completes a fast command').

Also resets the shell-mode cwd singleton between tmpdir instances so a
cd from one test cannot leak into the next.
Keep dev's deletion of sync-zed-extension.yml; PR #28 only swapped
runner labels in a workflow dev has since removed.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant architectural changes, including a new layout design system, a refactored server management controller, and updated performance diagnostics. It also migrates several components to a V2 UI library and improves the robustness of session tab management. I have identified several critical and high-severity issues, primarily related to parameter shadowing in the tab navigation component, potential null pointer dereferences in draft promotion, and missing imports in error handling blocks. Please address these bugs before merging.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +191 to +258
<Show when={props.session()}>
{(session) => {
return (
<a
data-slot="tab-link"
data-titlebar-tab-link
href={props.href}
draggable={false}
onDragStart={(event) => {
event.preventDefault()
event.stopPropagation()
}}
onClick={(event) => {
event.preventDefault()
if (editing()) return
if (props.suppressNavigation?.()) return
props.onNavigate()
}}
class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
>
<span data-slot="project-avatar-slot">
<SessionTabAvatar
project={project()}
directory={session().directory}
sessionId={session().id}
activeServer={props.activeServer}
/>
</span>
<span
ref={(el) => {
titleEl = el
titleEl.textContent = session().title
}}
data-slot="tab-title"
data-titlebar-tab-title
class="min-w-0 flex-1 outline-none leading-4"
classList={{
"overflow-hidden text-clip whitespace-nowrap": !editing(),
"select-text": editing(),
}}
contenteditable={editing() ? true : undefined}
onDblClick={openRename}
onKeyDown={(event) => {
event.stopPropagation()
if (event.key === "Enter") {
event.preventDefault()
void closeRename(true)
return
}
if (event.key !== "Escape") return
event.preventDefault()
titleEl.textContent = session().title
void closeRename(false)
}}
onBlur={() => void closeRename(true)}
onPointerDown={(event) => {
if (!editing()) return
event.stopPropagation()
}}
onClick={(event) => {
if (!editing()) return
event.preventDefault()
}}
/>
</a>
)
}}
</Show>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical Reactivity Bug: Parameter Shadowing and Accessor Invocation

Inside <Show when={props.session()}>, the render function parameter is named session (line 192). This shadows the accessor function props.session (or any helper named session).

Because session is the parameter representing the resolved Session object (not a function), calling session() as a function on lines 214, 215, 222, and 242 will throw a TypeError: session is not a function at runtime, causing a complete crash of the tab navigation.

To fix this, we should simply access the properties directly on the session parameter (e.g., session.directory, session.id, session.title) without invoking it as a function.

      <Show when={props.session()}>
        {(session) => {
          return (
            <a
              data-slot="tab-link"
              data-titlebar-tab-link
              href={props.href}
              draggable={false}
              onDragStart={(event) => {
                event.preventDefault()
                event.stopPropagation()
              }}
              onClick={(event) => {
                event.preventDefault()
                if (editing()) return
                if (props.suppressNavigation?.()) return
                props.onNavigate()
              }}
              class="flex h-full min-w-0 flex-1 flex-row items-center gap-1.5 text-[13px] font-medium text-v2-text-text-faint group-data-[active='true']:text-v2-text-text-base group-data-[editing='true']:text-v2-text-text-base [-webkit-user-drag:none]"
            >
              <span data-slot="project-avatar-slot">
                <SessionTabAvatar
                  project={project()}
                  directory={session.directory}
                  sessionId={session.id}
                  activeServer={props.activeServer}
                />
              </span>
              <span
                ref={(el) => {
                  titleEl = el
                  titleEl.textContent = session.title
                }}
                data-slot="tab-title"
                data-titlebar-tab-title
                class="min-w-0 flex-1 outline-none leading-4"
                classList={{
                  "overflow-hidden text-clip whitespace-nowrap": !editing(),
                  "select-text": editing(),
                }}
                contenteditable={editing() ? true : undefined}
                onDblClick={openRename}
                onKeyDown={(event) => {
                  event.stopPropagation()
                  if (event.key === "Enter") {
                    event.preventDefault()
                    void closeRename(true)
                    return
                  }
                  if (event.key !== "Escape") return
                  event.preventDefault()
                  titleEl.textContent = session.title
                  void closeRename(false)
                }}
                onBlur={() => void closeRename(true)}
                onPointerDown={(event) => {
                  if (!editing()) return
                  event.stopPropagation()
                }}
                onClick={(event) => {
                  if (!editing()) return
                  event.preventDefault()}
                }
              />
            </a>
          )
        }}
      </Show>

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this file is not authored in this PR. It arrived via the upstream sync (sst/opencode dev, merged to our dev in #31) and only appears in this diff because the base branch (LAC-2385/blacksmith-to-github-runners, PR #28) predates that sync merge. This PR only changes unit-test files (packages/opencode/test/**) to fix CI failures on GitHub-hosted runners (LAC-2386). Upstream code concerns are tracked against upstream, not patched in a test-fix PR — patching them here would create merge conflicts on the next sync.

Comment on lines +380 to +383
const draftID = search.draftId
if (draftID) tabs.promoteDraft(draftID, { server: tabs.draft(draftID).server, sessionId: session.id })
else navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
submission.retarget(prompt.capture({ dir: base64Encode(sessionDirectory), id: session.id }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

High Severity: Potential Null Pointer Dereference

tabs.draft(draftID) can return undefined if the draft is not found or has been cleared. Accessing .server on undefined will throw a TypeError at runtime. We should safely retrieve the draft first before promoting it.

        const draftID = search.draftId
        const draft = draftID ? tabs.draft(draftID) : undefined
        if (draftID && draft) {
          tabs.promoteDraft(draftID, { server: draft.server, sessionId: session.id })
        } else {
          navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
        }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this file is not authored in this PR. It arrived via the upstream sync (sst/opencode dev, merged to our dev in #31) and only appears in this diff because the base branch (LAC-2385/blacksmith-to-github-runners, PR #28) predates that sync merge. This PR only changes unit-test files (packages/opencode/test/**) to fix CI failures on GitHub-hosted runners (LAC-2386). Upstream code concerns are tracked against upstream, not patched in a test-fix PR — patching them here would create merge conflicts on the next sync.

Comment on lines +348 to +350
)

const AdvancedSection = () => (

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

High Severity: Circular Dependency & Logic Bug

When checked is false (meaning the user turned OFF the new layout designs), they are switching back to the legacy layout and should be shown the legacy settings dialog (@/components/dialog-settings).

However, the code currently imports @/components/settings-v2 (which is the V2 settings dialog) and shows DialogSettings from V2 again, creating a circular dependency and a broken user experience. We should import @/components/dialog-settings instead.

                void import("@/components/dialog-settings").then((module) => {
                  dialog.show(() => <module.DialogSettings />)
                })

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this file is not authored in this PR. It arrived via the upstream sync (sst/opencode dev, merged to our dev in #31) and only appears in this diff because the base branch (LAC-2385/blacksmith-to-github-runners, PR #28) predates that sync merge. This PR only changes unit-test files (packages/opencode/test/**) to fix CI failures on GitHub-hosted runners (LAC-2386). Upstream code concerns are tracked against upstream, not patched in a test-fix PR — patching them here would create merge conflicts on the next sync.

await setDefault(null)
}
} catch (err) {
showRequestError(language, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

High Severity: Missing Import / ReferenceError

showRequestError is called in the catch block of handleRemove, but it is not imported in this file. This will cause a runtime crash if an error occurs. We should import showRequestError or use showToast directly.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this file is not authored in this PR. It arrived via the upstream sync (sst/opencode dev, merged to our dev in #31) and only appears in this diff because the base branch (LAC-2385/blacksmith-to-github-runners, PR #28) predates that sync merge. This PR only changes unit-test files (packages/opencode/test/**) to fix CI failures on GitHub-hosted runners (LAC-2386). Upstream code concerns are tracked against upstream, not patched in a test-fix PR — patching them here would create merge conflicts on the next sync.

const wslServers = useFilteredWslServers(() => store.filter)

const showSearch = createMemo(
() => controller.sortedItems().filter((item) => !isWslServer(item)).length + wslServers().length > 1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Medium Severity: Potential TypeError on Undefined Resource

wslServers() is returned by useFilteredWslServers and can be undefined during loading or if the resource hasn't resolved yet. Accessing .length directly on undefined will throw a TypeError. We should use optional chaining wslServers()?.length to make this safe.

Suggested change
() => controller.sortedItems().filter((item) => !isWslServer(item)).length + wslServers().length > 1,
() => controller.sortedItems().filter((item) => !isWslServer(item)).length + (wslServers()?.length ?? 0) > 1,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this file is not authored in this PR. It arrived via the upstream sync (sst/opencode dev, merged to our dev in #31) and only appears in this diff because the base branch (LAC-2385/blacksmith-to-github-runners, PR #28) predates that sync merge. This PR only changes unit-test files (packages/opencode/test/**) to fix CI failures on GitHub-hosted runners (LAC-2386). Upstream code concerns are tracked against upstream, not patched in a test-fix PR — patching them here would create merge conflicts on the next sync.


<div class="settings-v2-tab-body settings-v2-servers">
<Show
when={filtered().length > 0 || wslServers().length > 0}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Medium Severity: Potential TypeError on Undefined Resource

wslServers() can be undefined during loading. Accessing .length directly on undefined will throw a TypeError. We should use optional chaining wslServers()?.length to make this safe.

Suggested change
when={filtered().length > 0 || wslServers().length > 0}
when={filtered().length > 0 || (wslServers()?.length ?? 0) > 0}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this file is not authored in this PR. It arrived via the upstream sync (sst/opencode dev, merged to our dev in #31) and only appears in this diff because the base branch (LAC-2385/blacksmith-to-github-runners, PR #28) predates that sync merge. This PR only changes unit-test files (packages/opencode/test/**) to fix CI failures on GitHub-hosted runners (LAC-2386). Upstream code concerns are tracked against upstream, not patched in a test-fix PR — patching them here would create merge conflicts on the next sync.

export function HelpButton() {
if (import.meta.env.VITE_OPENCODE_CHANNEL !== "dev") return null

const [state, setState] = /* persisted(Persist.global("help-button"), */ createStore({ dismissed: false }) /* ) */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Medium Severity: Commented-out Code

The persisted wrapper is commented out, meaning the dismissed state of the help button is not persisted across page reloads. If this is intentional, please clean up the commented-out code to maintain readability. Otherwise, uncomment it to enable persistence.

Suggested change
const [state, setState] = /* persisted(Persist.global("help-button"), */ createStore({ dismissed: false }) /* ) */
const [state, setState] = createStore({ dismissed: false })

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this file is not authored in this PR. It arrived via the upstream sync (sst/opencode dev, merged to our dev in #31) and only appears in this diff because the base branch (LAC-2385/blacksmith-to-github-runners, PR #28) predates that sync merge. This PR only changes unit-test files (packages/opencode/test/**) to fix CI failures on GitHub-hosted runners (LAC-2386). Upstream code concerns are tracked against upstream, not patched in a test-fix PR — patching them here would create merge conflicts on the next sync.

…nners (LAC-2386)

- execution-mode-color-bar.test.ts read the prompt component source from
  its pre-sync location (packages/opencode/src/cli/cmd/tui/component/
  prompt/index.tsx); the upstream sync moved it to packages/tui/src/
  component/prompt/index.tsx, so the module-scope readFileSync threw
  ENOENT and bun reported an unhandled error between tests. Point the
  read at the new location — all 14 structural assertions still match.

- run-process.test.ts gave the unknown-model regression test a 15s
  subprocess budget and asserted wall-clock < 15s. Cold CLI startup on
  loaded 2-core GitHub-hosted runners was observed at 15.2s, tripping
  the assertion without any hang. Raise budget to 30s (outer timeout
  60s, matching sibling tests); a genuine anomalyco#27371-style hang still runs
  to the cutoff and fails the duration assertion.
@lacymorrow lacymorrow merged commit e3cb8f7 into LAC-2385/blacksmith-to-github-runners Jul 9, 2026
12 of 13 checks passed
@lacymorrow lacymorrow deleted the LAC-2386/fix-shell-cwd-tests branch July 9, 2026 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.