Skip to content

refactor: migrate selected runtime schemas to Effect Schema#280

Merged
Astro-Han merged 1 commit intodevfrom
claude/upstream-sync-209-pr3-effect-schema
Apr 28, 2026
Merged

refactor: migrate selected runtime schemas to Effect Schema#280
Astro-Han merged 1 commit intodevfrom
claude/upstream-sync-209-pr3-effect-schema

Conversation

@Astro-Han
Copy link
Copy Markdown
Owner

@Astro-Han Astro-Han commented Apr 27, 2026

Summary

Squashes upstream's Effect Schema migration batch (16 PRs, dated Apr 21–24 2026) into one PawWork commit. Selectively adopts the schema-definition-layer changes while preserving PawWork's namespace-API and Zod-typed BusEvent layers where divergence is too deep.

Upstream PRs included: #23716, #23740, #23744, #23745, #23747, #23749, #23752, #23754, #23756, #23757, #23763, #24005, #24040, #24169, #24213

Why

Stage D of the foundation sync (#209). The schema-definition layer (Config.Info, ConfigPermission.Info, ConfigAgent.Info, several */schema.ts files, util/effect-zod walker) is core runtime work — this PR adopts those. Per @yuhan: "Effect Schema is core runtime work."

The consumer layer (session, bus, provider, snapshot, permission, mcp namespaces) has too much PawWork divergence to take upstream's redesign wholesale. Those stay on PawWork's existing Zod / namespace API; the schema layer changes still flow through via the .zod accessor pattern.

What landed (24 files)

  • Config / Permission / Agent schemas migrated to Effect Schema canonical form. PawWork callers use Config.Info.zod (server/instance/{config,global}.ts, script/schema.ts).
  • Schema-only files migrated in control-plane, permission, project, pty, question, sync, tool/schema.ts, session/schema.ts, tool/edit.ts, tool/todo.ts.
  • util/effect-zod.ts walker enhanced to derive .zod from Effect Schema at runtime, plus walker tests.
  • packages/sdk/js/src/v2/gen/types.gen.ts regenerated to reflect schema shape changes (Stage I would've done this anyway).
  • Test fixtures updated (config.test.ts, permission/next.test.ts, compaction.test.ts, session.test.ts, effect-zod.test.ts).

What was preserved (PawWork divergence)

  • session/{session,message-v2,prompt,export,message,revert,summary,todo,status,compaction,projectors}.ts — PawWork's session domain stays Zod-shaped. MessageV2's FileSource/SymbolSource/ResourceSource carry PawWork-only attachment metadata that upstream's Effect Schema variants drop.
  • bus/bus-event.ts — PawWork keeps Zod-typed BusEvent.define. ~30 PawWork tests + divergent consumers (server/instance/*, acp/agent.ts, file/watcher.ts) read evt.properties as inferred type, not unknown.
  • {snapshot,permission,mcp,provider}/index.ts — PawWork uses a "namespace + XValue alias" pattern that upstream redesigned to flat exports. Keeping HEAD preserves Snapshot.track, Permission.ask, Mcp.add, Provider.list.
  • provider/{provider,auth,models}.ts — PawWork's volcengine/zen integration carries divergent code paths (VOLCENGINE_PLAN_* constants, HTTP-Referer/X-Title carve-out per project_rename_blockers.md).
  • {file,project,pty,sync,ide,lsp/client,command,control-plane/workspace,worktree,question,util/schema}/{index,*}.ts — kept PawWork API surface for callers.

Skipped commit: 93940a1859 (provider domain Effect Schema migration) — entire diff sat on PawWork carve-out files; reapplied through PawWork-local provider when divergence settles.

Related Issue

#209

How To Verify

bun install
bun run --cwd packages/core typecheck
bun run --cwd packages/opencode typecheck
bun run --cwd packages/util typecheck
bun run --cwd packages/ui typecheck
bun run --cwd packages/desktop-electron typecheck

All 5 packages typecheck clean.

Stacked on

Base is claude/upstream-sync-209-pr2-moves (#266). Will auto-rebase to dev after #265#266 chain merges.

Checklist

  • I linked the related issue ([Feature] Track selective upstream backports from opencode v1.14.17-v1.14.22 #209)
  • This PR has type, scope, and priority labels (upstream / harness / P3)
  • I listed the relevant verification steps
  • No visible UI changes — N/A
  • No desktop/packaging/updater impact — N/A (runtime schema only)
  • I considered docs/dependencies — packages/sdk/js/v2/gen/types.gen.ts regenerated; no new deps
  • Targeting dev (via stacked PR), Conventional Commits English title

Summary by CodeRabbit

  • Bug Fixes

    • Permission rules now evaluate deterministically so wildcard/legacy entries no longer unpredictably override specific rules.
    • File diff additions/deletions computed in a single pass for more accurate results.
  • Refactor

    • Unified schema→Zod integration so validation, OpenAPI responses, and docs use the same strict shape.
    • Permission input normalization simplified and legacy-key aliases suppressed.
  • Tests

    • Added/updated tests for permission canonicalization and Zod-backed config parsing.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Effect schemas are now canonical with derived Zod statics (Info.zod); ZodPreprocess removed; Zod derivation centralized via a shared adapter; permission decoding/order logic changed (wildcards prioritized, legacy aliases suppressed); OpenAPI, validators, and tests updated to use Config.Info.zod.

Changes

Cohort / File(s) Summary
Core Schema Utilities
packages/opencode/src/util/effect-zod.ts
Removed ZodPreprocess; centralized Effect→Zod derivation; added bodyWithChecks, zodObject(...); apply description/meta consistently; changed walker behavior for overrides.
Config Surface & JSON Schema
packages/opencode/src/config/config.ts, packages/opencode/script/schema.ts
Made Effect Info canonical and expose Info.zod; switched JSON schema generation and runtime parsing sites to use Config.Info.zod; removed Config.parse re-export.
Permission Config & Runtime
packages/opencode/src/config/permission.ts, packages/opencode/src/permission/index.ts
Replaced preprocess-based preservation with explicit decode-time normalization; permission input accepts action shorthand or object; canonicalized known permission keys ordering and prioritize wildcard keys; legacy alias entries suppressed when canonical key exists.
Agent Config
packages/opencode/src/config/agent.ts
permission field now references ConfigPermission.Info Zod-compatible schema directly; removed intermediate PermissionRef/override helper.
Identifier/ID Zod Integration
packages/opencode/src/*/schema.ts
permission/schema.ts, project/schema.ts, control-plane/schema.ts, pty/schema.ts, question/schema.ts, session/schema.ts, sync/schema.ts, tool/schema.ts
Standardized Zod derivation using shared zod(schema) adapter and ZodOverride annotations; removed direct z.custom(...) and inline zod imports across ID schemas.
Server Routes / OpenAPI
packages/opencode/src/server/instance/config.ts, packages/opencode/src/server/instance/global.ts
Switched OpenAPI response schemas and request validators for config endpoints to use Config.Info.zod.
Tools & Minor Logic
packages/opencode/src/tool/edit.ts, packages/opencode/src/tool/todo.ts
Simplified diff additions/deletions counting to a single-pass aggregate; todowrite parameters now use an explicit inline Zod object shape for todos.
Tests
packages/opencode/test/...
test/config/config.test.ts, test/permission/next.test.ts, test/session/*.test.ts, test/util/effect-zod.test.ts
Updated tests to consume Config.Info.zod, added permission ordering/regression tests, removed ZodPreprocess tests, and adjusted small casts/assertions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

upstream, enhancement, javascript

Poem

🐰 I hopped through schemas, tidy and bright,
Effect first, Zod follows — all stitched right.
Wildcards leap forward, old aliases hide,
Tests applaud the order, tidy and tried.
A rabbit cheers: neat code, moonlit night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: migrating runtime schemas to Effect Schema, which is the core objective across all 24 affected files.
Description check ✅ Passed The description thoroughly covers all required template sections: Summary explains the consolidated upstream migration; Why establishes the strategic context (Stage D foundation sync); Related Issue links #209; How To Verify provides specific typecheck commands; Checklist items are completed with clear explanations of preserved divergences and skipped work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/upstream-sync-209-pr3-effect-schema

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

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

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 refactors the configuration and permission schemas to use Effect Schema as the canonical source of truth, deriving Zod schemas through a zod() walker and a withStatics utility. It removes the __originalKeys and ZodPreprocess workarounds for permission ordering in favor of an order-independent model and standardizes identifier schemas across the codebase. Feedback was provided to improve the isZodType utility by using instanceof z.ZodType to ensure reliable schema identification and prevent redundant AST processing.

Comment thread packages/opencode/src/util/effect-zod.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/test/session/session.test.ts (1)

126-166: ⚠️ Potential issue | 🟠 Major

Prevent Bus subscription leaks on failed assertions (use try/finally).

unsub() (and SessionNs.remove(info.id)) run only after all assertions. If an assertion fails, the subscription may remain active and interfere with later tests.

Wrap the “wait + assertions” section in try/finally so unsub() + cleanup always run:

Suggested diff
           const info = await SessionNs.create({})
@@
           const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, (event) => {
             received = event.properties.part as MessageV2.Part
           })
-
-          const tokens = {
-            total: 1500,
-            input: 500,
-            output: 800,
-            reasoning: 200,
-            cache: { read: 100, write: 50 },
-          }
-          const partInput = {
-            id: PartID.ascending(),
-            messageID,
-            sessionID: info.id,
-            type: "step-finish" as const,
-            reason: "stop",
-            cost: 0.005,
-            tokens,
-          }
-          await SessionNs.updatePart(partInput)
-          await new Promise((resolve) => setTimeout(resolve, 100))
-
-          expect(received).toBeDefined()
-          expect(received!.type).toBe("step-finish")
-          const finish = received as MessageV2.StepFinishPart
-          expect(finish.tokens.input).toBe(500)
-          expect(finish.tokens.output).toBe(800)
-          expect(finish.tokens.reasoning).toBe(200)
-          expect(finish.tokens.total).toBe(1500)
-          expect(finish.tokens.cache.read).toBe(100)
-          expect(finish.tokens.cache.write).toBe(50)
-          expect(finish.cost).toBe(0.005)
-          expect(received).not.toBe(partInput)
-
-          unsub()
-          await SessionNs.remove(info.id)
+          try {
+            const tokens = {
+              total: 1500,
+              input: 500,
+              output: 800,
+              reasoning: 200,
+              cache: { read: 100, write: 50 },
+            }
+            const partInput = {
+              id: PartID.ascending(),
+              messageID,
+              sessionID: info.id,
+              type: "step-finish" as const,
+              reason: "stop",
+              cost: 0.005,
+              tokens,
+            }
+            await SessionNs.updatePart(partInput)
+            await new Promise((resolve) => setTimeout(resolve, 100))
+
+            expect(received).toBeDefined()
+            expect(received!.type).toBe("step-finish")
+            const finish = received as MessageV2.StepFinishPart
+            expect(finish.tokens.input).toBe(500)
+            expect(finish.tokens.output).toBe(800)
+            expect(finish.tokens.reasoning).toBe(200)
+            expect(finish.tokens.total).toBe(1500)
+            expect(finish.tokens.cache.read).toBe(100)
+            expect(finish.tokens.cache.write).toBe(50)
+            expect(finish.cost).toBe(0.005)
+            expect(received).not.toBe(partInput)
+          } finally {
+            unsub()
+            await SessionNs.remove(info.id)
+          }
         },
       })

This makes the test suite more resilient to intermittent failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/session.test.ts` around lines 126 - 166, The
test currently subscribes with Bus.subscribe (captured by unsub) and calls
unsub() + SessionNs.remove(info.id) only after assertions, risking subscription
leaks on failures; wrap the section that awaits the event and performs all
expect(...) checks in a try/finally block so that in the finally you always call
unsub() and await SessionNs.remove(info.id) (leave PartID.ascending(),
SessionNs.updatePart and the await new Promise(...) untouched), ensuring cleanup
runs regardless of assertion outcomes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/config/permission.ts`:
- Around line 21-31: The StructWithRest decoder reorders top-level permission
keys which breaks the precedence expected by Permission.evaluate
(last-match-wins) because Permission.fromConfig currently flattens
Object.entries(permission) in the decoded order; to fix, preserve original
insertion order or explicitly sort entries in Permission.fromConfig before
building rules so wildcards do not incorrectly override explicit rules — update
Permission.fromConfig to read and use the preserved __originalKeys (if present
from StructWithRest) or perform a stable sort that places specific keys before
wildcards (referencing StructWithRest, Permission.fromConfig, and
Permission.evaluate) so config precedence matches tests.

In `@packages/opencode/src/session/projectors.ts`:
- Line 65: Replace the blind type assertion on data.info before inserting into
DB: instead of using "as Session.Info" pass a validated/narrowed Session.Info
object to Session.toRow; validate the payload (e.g., with an explicit type
guard, runtime checks, or a schema like zod) and handle failures (log/skip or
throw) before calling db.insert(SessionTable).values(Session.toRow(...)).run(),
referencing the data.info variable, Session.toRow, SessionTable and the
db.insert call to locate the change.

In `@packages/opencode/src/util/effect-zod.ts`:
- Around line 43-50: Replace the undocumented private-property check in
isZodType and guard the cast in zodObject: change isZodType(value) to use the
public API (e.g., return value instanceof z.ZodType) and in
zodObject(Schema.Top) after computing derived call a public instanceof check
(e.g., derived instanceof z.ZodObject) before casting; if the check fails, throw
or return a clear error instead of force-casting. Update references to isZodType
and zodObject accordingly so the code relies only on zod's public types.

In `@packages/opencode/test/session/session.test.ts`:
- Around line 123-129: The test currently assigns event.properties.part to
received as MessageV2.Part which can capture any union member; update the
Bus.subscribe handler (subscribing to MessageV2.Event.PartUpdated) to first
check the runtime discriminator (e.g., event.properties.part.type ===
"step-finish") and only then assign to received casted to
MessageV2.StepFinishPart (preserving any readonly→mutable cast if needed); this
ensures received is the narrow StepFinishPart type and prevents capturing
unrelated PartUpdated events.

---

Outside diff comments:
In `@packages/opencode/test/session/session.test.ts`:
- Around line 126-166: The test currently subscribes with Bus.subscribe
(captured by unsub) and calls unsub() + SessionNs.remove(info.id) only after
assertions, risking subscription leaks on failures; wrap the section that awaits
the event and performs all expect(...) checks in a try/finally block so that in
the finally you always call unsub() and await SessionNs.remove(info.id) (leave
PartID.ascending(), SessionNs.updatePart and the await new Promise(...)
untouched), ensuring cleanup runs regardless of assertion outcomes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 953bf1f5-7134-4337-bea3-1ccd0bf19bcc

📥 Commits

Reviewing files that changed from the base of the PR and between ae1e1d7 and 0356b26.

⛔ Files ignored due to path filters (1)
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (23)
  • packages/opencode/script/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/session/projectors.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-opencode
  • GitHub Check: unit-desktop
  • GitHub Check: typecheck
  • GitHub Check: analyze-js-ts
  • GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (2)
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/projectors.ts
  • packages/opencode/test/config/config.test.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/config/config.test.ts
🧠 Learnings (36)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Class` for multi-field data in Effect schemas
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/control-plane/schema.ts
📚 Learning: 2026-04-21T16:57:23.748Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/control-plane/schema.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Class` for multi-field data in Effect schemas

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/control-plane/schema.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/drizzle.config.ts : Configure Drizzle Kit migrations with schema path `./src/**/*.sql.ts` and output path `./migration`

Applied to files:

  • packages/opencode/script/schema.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/src/**/*.sql.ts : Drizzle schema should live in `src/**/*.sql.ts`

Applied to files:

  • packages/opencode/script/schema.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/control-plane/schema.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-27T11:19:24.963Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-27T11:18:47.332Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.TaggedErrorClass` for typed errors in Effect schemas

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/control-plane/schema.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-27T11:18:47.596Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/agent/agent.test.ts:440-447
Timestamp: 2026-04-27T11:18:47.596Z
Learning: In `packages/opencode/test/agent/agent.test.ts` (Astro-Han/pawwork), all agent-permission tests intentionally use the manual `tmpdir()` + `Instance.provide(...)` pattern. Do not flag individual tests in this file for conversion to `provideTmpdirInstance(...)` or `provideInstance(...)`; a full harness migration would be a separate PR if the pattern ever needs to change.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` services when those concerns are already inside Effect code

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O in Effect services

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.fn("Domain.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer `.pipe()` wrappers

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-21T16:57:20.257Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:20.257Z
Learning: In packages/opencode/src/config/ config modules, treat both `Schema.Struct` and `Schema.Class` as valid schema patterns and do not require converting `Schema.Struct` to `Schema.Class`. If `Schema.Struct` is used in a config schema (e.g., in files like `command.ts`, `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`), accept it as intentional; the selection should be based on upstream alignment and the local schema context, not on a rule that one form must replace the other.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/projectors.ts
📚 Learning: 2026-04-27T12:59:45.694Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:45.694Z
Learning: In session prompt/diagnostics tests (e.g., when verifying injected LLM “recovery reminder” copy), do not assert a single exact reminder phrasing tied to signature kind. If a scenario can produce both `input:` and `target:` signatures (for example, a `read` tool call with a `filePath` parameter), accept either phrasing: the input-repeat variant may say “repeated the same tool input 3 times” (literal count), and the target-repeat variant may say “failed against the same target multiple times” (“multiple times” without a count). Your assertions should allow both variants rather than narrowing to only the input-variant text.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-26T15:35:36.505Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 245
File: packages/opencode/src/question/index.ts:21-24
Timestamp: 2026-04-26T15:35:36.505Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/question/index.ts`), `Question.Option.description` is intentionally **required** (`z.string()`, not `.optional()`). This matches the upstream opencode contract and all current callers (e.g. `PlanExitTool`) always supply a description. The defensive `<Show when={props.description}>` rendering in `session-question-dock.tsx` is a standard guard, not a signal that the field is intended to be optional. Do NOT suggest making `Option.description` optional without a dedicated follow-up that covers schema + tool description + dock copy + tests.

Applied to files:

  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/tool/todo.ts
📚 Learning: 2026-04-27T10:32:59.274Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:32:59.274Z
Learning: In pawwork’s session diagnostics/loop error handling (e.g., packages/opencode/src/session/diagnostics.ts), keep `loopLastError` stored as raw/unscrubbed error text in loop metadata. The intended trust boundary for sensitive-data scrubbing is the renderer: `LoopRenderer.render` must apply `scrubErrorText` before any user/model-facing output. Do not recommend scrubbing `loopLastError` at the `observeToolError` call site, because failed tool parts already store raw errors in `state.error` (pre-existing leak) and scrubbing only `loopLastError` would be lossy without closing the gap. If export-side hardening is required, implement a single sanitizer pass in the export layer over both `state.error` and the loop/metadata fields, rather than sanitizing only `loopLastError` earlier in the storage path.

Applied to files:

  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/session/projectors.ts
📚 Learning: 2026-04-27T10:33:08.974Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:08.974Z
Learning: For Astro-Han/pawwork session processing code under packages/opencode/src/session/ (e.g., prompt.ts and processor.ts), this specific loop-gate TOCTOU/race between buildLoopContext() and recordSyntheticBlock/recordSyntheticStop is intentionally handled via idempotence guards (re-check sigKey presence and hasStopped inside the record helpers). Do not treat the absence of a per-parent Effect.Mutex as a blocking review issue for this known edge case; the residual window should only create extra synthetic parts and must not change the "turn ends" contract. Respect the existing code comment documenting the trade-off, and only escalate to a full-mutex approach if the race is observed in practice.

Applied to files:

  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/session/projectors.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually

Applied to files:

  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-24T06:50:05.142Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.

Applied to files:

  • packages/opencode/src/control-plane/schema.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/opencode/test/config/config.test.ts
🔇 Additional comments (16)
packages/opencode/src/tool/edit.ts (1)

149-159: Good simplification of diff stat computation.

Single-pass accumulation is cleaner and avoids mutating filediff fields in a follow-up pass.

packages/opencode/src/tool/todo.ts (1)

7-21: Inline todo parameter shape looks correct and type-safe for tool execution.

The updated parameters shape is consistent with the expected todo payload and keeps execute inference explicit.

packages/opencode/src/util/effect-zod.ts (1)

62-70: Walker layering refactor is clean.

Applying descriptions/refs after base derivation and centralizing checks in bodyWithChecks improves consistency without changing behavior intent.

Also applies to: 72-87

packages/opencode/test/util/effect-zod.test.ts (1)

5-5: Test import update matches the schema adapter surface.

Removing ZodPreprocess from imports is consistent with the runtime removal and keeps the test module aligned.

packages/opencode/src/project/schema.ts (1)

3-3: ProjectID migration to shared zod(schema) adapter looks good.

This keeps the branded Effect schema as source-of-truth while preserving the static Zod accessor.

Also applies to: 13-13

packages/opencode/src/session/schema.ts (1)

4-4: Session identifier schema migration is consistent and safe.

Using zod(s) with ZodOverride keeps the existing ID validation semantics while centralizing derivation.

Also applies to: 11-11, 21-21, 31-31

packages/opencode/src/tool/schema.ts (1)

4-4: ToolID schema update is aligned with the migration pattern.

The switch to shared zod(schema) keeps behavior centralized without changing the brand/override contract.

Also applies to: 14-14

packages/opencode/src/pty/schema.ts (1)

4-4: PtyID migration is coherent with the shared effect-zod approach.

The new override + zod(schema) pairing preserves ID validation intent while reducing duplicated Zod wiring.

Also applies to: 7-7, 14-14

packages/opencode/src/control-plane/schema.ts (1)

4-8: LGTM: WorkspaceID now cleanly uses the shared Effect→Zod bridge.

The override annotation plus zod(schema) static keeps validation behavior aligned while preserving branded typing.

Also applies to: 16-16

packages/opencode/src/question/schema.ts (1)

4-4: LGTM: QuestionID migration is consistent and preserves schema intent.

Using ZodOverride with zod(this) keeps the Newtype contract and removes manual cast-based Zod plumbing.

Also applies to: 7-10, 15-15

packages/opencode/src/sync/schema.ts (1)

4-4: LGTM: EventID now follows the centralized override + derive pattern.

The change is coherent with the rest of the ID schema migrations.

Also applies to: 7-7, 11-11

packages/opencode/src/permission/schema.ts (1)

4-4: LGTM: PermissionID migration is correct and consistent with adapter-driven Zod generation.

Also applies to: 7-10, 15-15

packages/opencode/script/schema.ts (1)

56-56: LGTM: schema emission now targets Config.Info.zod, matching the migrated validation surface.

packages/opencode/test/session/compaction.test.ts (1)

178-178: LGTM: test baseline config now correctly uses Config.Info.zod.parse({}).

packages/opencode/test/config/config.test.ts (1)

2950-2950: LGTM: managed-plist parsing tests are consistently updated to Config.Info.zod.

This keeps test validation aligned with the new schema surface.

Also applies to: 2982-2982, 3002-3002, 3032-3032, 3049-3049

packages/opencode/src/server/instance/config.ts (1)

26-26: LGTM: config routes now consistently use Config.Info.zod for both OpenAPI schemas and request validation.

Also applies to: 47-47, 54-54

Comment thread packages/opencode/src/config/permission.ts
Comment thread packages/opencode/src/session/projectors.ts Outdated
Comment thread packages/opencode/src/util/effect-zod.ts
Comment thread packages/opencode/test/session/session.test.ts
@Astro-Han Astro-Han force-pushed the claude/upstream-sync-209-pr3-effect-schema branch from 0356b26 to 205cdf4 Compare April 28, 2026 01:23
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/test/config/config.test.ts (1)

2116-2135: 🛠️ Refactor suggestion | 🟠 Major

Use tmpdir({ config: ... }) instead of manual config file writes in this test setup.

This case manually writes opencode.json in init; please use the fixture’s config option for consistency with test helpers.

♻️ Suggested refactor
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      await Filesystem.write(
-        path.join(dir, "opencode.json"),
-        JSON.stringify({
-          $schema: "https://opencode.ai/config.json",
-          permission: {
-            "*": "deny",
-            edit: "ask",
-            write: "ask",
-            external_directory: "ask",
-            read: "allow",
-            todowrite: "allow",
-            "thoughts_*": "allow",
-            "reasoning_model_*": "allow",
-            "tools_*": "allow",
-            "pr_comments_*": "allow",
-          },
-        }),
-      )
-    },
-  })
+  await using tmp = await tmpdir({
+    config: {
+      permission: {
+        "*": "deny",
+        edit: "ask",
+        write: "ask",
+        external_directory: "ask",
+        read: "allow",
+        todowrite: "allow",
+        "thoughts_*": "allow",
+        "reasoning_model_*": "allow",
+        "tools_*": "allow",
+        "pr_comments_*": "allow",
+      },
+    },
+  })

As per coding guidelines: Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/config/config.test.ts` around lines 2116 - 2135,
Replace the manual opencode.json write inside the tmpdir init callback by using
the tmpdir fixture's config option: remove the Filesystem.write call that writes
"opencode.json" and instead pass a partial Config.Info object to tmpdir({
config: { ... } }); locate the test's tmpdir invocation and the init async (dir)
=> { ... } block and move the JSON object (schema and permission map) into the
tmpdir call as its config property so the fixture creates opencode.json
automatically.
♻️ Duplicate comments (1)
packages/opencode/test/session/session.test.ts (1)

123-129: ⚠️ Potential issue | 🟡 Minor

Still narrow this subscription before assigning received.

The cast only fixes the readonly-vs-mutable type mismatch; it does not stop an unrelated PartUpdated event from overwriting received before the assertions. Please keep the runtime discriminator guard from the earlier review.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/session.test.ts` around lines 123 - 129, The
subscriber assigned to Bus.subscribe(MessageV2.Event.PartUpdated) currently
casts and writes to received directly, allowing unrelated PartUpdated events to
overwrite it; update the callback in Bus.subscribe so it first performs a
runtime discriminator guard (e.g., check event.properties.part.id or another
expected discriminant) before assigning to the local received variable, and keep
the existing unsubscribe logic (unsub) after the assertion to avoid leaks;
reference the MessageV2.Event.PartUpdated subscription, the
event.properties.part payload, the received variable, and the unsub cleanup when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/tool/todo.ts`:
- Around line 7-22: Replace the inline todo schema in the parameters constant
with the canonical Todo.Info to avoid duplication: confirm that Tool.define
still infers execute() when using z.object({ todos: z.array(Todo.Info) }) (or
z.array(Todo.Info) if parameters shape differs), and if inference fails, adjust
by wrapping Todo.Info in z.array(...) while keeping parameters typed as the same
constant name (parameters) so Tool.define and execute() continue to work; update
imports to reference Todo from packages/opencode/src/session/todo.ts and run
TypeScript checks to ensure no inference/type errors.

---

Outside diff comments:
In `@packages/opencode/test/config/config.test.ts`:
- Around line 2116-2135: Replace the manual opencode.json write inside the
tmpdir init callback by using the tmpdir fixture's config option: remove the
Filesystem.write call that writes "opencode.json" and instead pass a partial
Config.Info object to tmpdir({ config: { ... } }); locate the test's tmpdir
invocation and the init async (dir) => { ... } block and move the JSON object
(schema and permission map) into the tmpdir call as its config property so the
fixture creates opencode.json automatically.

---

Duplicate comments:
In `@packages/opencode/test/session/session.test.ts`:
- Around line 123-129: The subscriber assigned to
Bus.subscribe(MessageV2.Event.PartUpdated) currently casts and writes to
received directly, allowing unrelated PartUpdated events to overwrite it; update
the callback in Bus.subscribe so it first performs a runtime discriminator guard
(e.g., check event.properties.part.id or another expected discriminant) before
assigning to the local received variable, and keep the existing unsubscribe
logic (unsub) after the assertion to avoid leaks; reference the
MessageV2.Event.PartUpdated subscription, the event.properties.part payload, the
received variable, and the unsub cleanup when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b2b8e79a-702d-4135-9486-3976088cfb86

📥 Commits

Reviewing files that changed from the base of the PR and between 0356b26 and 205cdf4.

⛔ Files ignored due to path filters (1)
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (23)
  • packages/opencode/script/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-app
  • GitHub Check: unit-desktop
  • GitHub Check: unit-opencode
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (2)
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/src/config/config.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
🧠 Learnings (41)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Class` for multi-field data in Effect schemas
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.TaggedErrorClass` for typed errors in Effect schemas
📚 Learning: 2026-04-28T01:24:29.645Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-21T16:57:23.748Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Class` for multi-field data in Effect schemas

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T11:18:47.332Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-27T10:33:12.228Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T12:59:45.694Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:45.694Z
Learning: In session prompt/diagnostics tests (e.g., when verifying injected LLM “recovery reminder” copy), do not assert a single exact reminder phrasing tied to signature kind. If a scenario can produce both `input:` and `target:` signatures (for example, a `read` tool call with a `filePath` parameter), accept either phrasing: the input-repeat variant may say “repeated the same tool input 3 times” (literal count), and the target-repeat variant may say “failed against the same target multiple times” (“multiple times” without a count). Your assertions should allow both variants rather than narrowing to only the input-variant text.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/session/compaction.test.ts
📚 Learning: 2026-04-27T10:08:57.448Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/diagnostics.test.ts:313-318
Timestamp: 2026-04-27T10:08:57.448Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `SessionDiagnostics.LoopAction` is a TypeScript string literal union type (`"observe" | "block" | "stop"`), NOT a runtime enum. There is no runtime form to introspect via `Object.values()`. The test pattern `const all: SessionDiagnostics.LoopAction[] = ["observe", "block", "stop"]; expect(all).toHaveLength(3)` is intentional and correct — it provides compile-time exhaustiveness via the type annotation. Do NOT suggest replacing this with `Object.values()` introspection or flag the runtime length check as insufficient.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-24T06:50:05.142Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/control-plane/schema.ts
📚 Learning: 2026-04-25T12:52:39.403Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/session/export.ts:75-88
Timestamp: 2026-04-25T12:52:39.403Z
Learning: In `packages/opencode/src/session/export.ts` (Astro-Han/pawwork), the `extractReasonFromCause` function intentionally uses a lightweight cast-based inspection of the Cause `reasons` array instead of `Cause.failures()` / `Cause.defects()` utilities. This is a deliberate choice: the function is diagnostic-only (used to populate a best-effort reason string for export diagnostics), and the maintainer explicitly prefers not to add coupling to Effect's Cause API surface for this purpose. Do not flag this pattern as fragile or suggest replacing it with Cause utilities.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-27T11:18:47.596Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/agent/agent.test.ts:440-447
Timestamp: 2026-04-27T11:18:47.596Z
Learning: In `packages/opencode/test/agent/agent.test.ts` (Astro-Han/pawwork), all agent-permission tests intentionally use the manual `tmpdir()` + `Instance.provide(...)` pattern. Do not flag individual tests in this file for conversion to `provideTmpdirInstance(...)` or `provideInstance(...)`; a full harness migration would be a separate PR if the pattern ever needs to change.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.TaggedErrorClass` for typed errors in Effect schemas

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` services when those concerns are already inside Effect code

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O in Effect services

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.fn("Domain.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer `.pipe()` wrappers

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-21T16:57:20.257Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:20.257Z
Learning: In packages/opencode/src/config/ config modules, treat both `Schema.Struct` and `Schema.Class` as valid schema patterns and do not require converting `Schema.Struct` to `Schema.Class`. If `Schema.Struct` is used in a config schema (e.g., in files like `command.ts`, `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`), accept it as intentional; the selection should be based on upstream alignment and the local schema context, not on a rule that one form must replace the other.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-27T11:19:24.963Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-27T10:32:59.274Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:32:59.274Z
Learning: In pawwork’s session diagnostics/loop error handling (e.g., packages/opencode/src/session/diagnostics.ts), keep `loopLastError` stored as raw/unscrubbed error text in loop metadata. The intended trust boundary for sensitive-data scrubbing is the renderer: `LoopRenderer.render` must apply `scrubErrorText` before any user/model-facing output. Do not recommend scrubbing `loopLastError` at the `observeToolError` call site, because failed tool parts already store raw errors in `state.error` (pre-existing leak) and scrubbing only `loopLastError` would be lossy without closing the gap. If export-side hardening is required, implement a single sanitizer pass in the export layer over both `state.error` and the loop/metadata fields, rather than sanitizing only `loopLastError` earlier in the storage path.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-27T10:33:08.974Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:08.974Z
Learning: For Astro-Han/pawwork session processing code under packages/opencode/src/session/ (e.g., prompt.ts and processor.ts), this specific loop-gate TOCTOU/race between buildLoopContext() and recordSyntheticBlock/recordSyntheticStop is intentionally handled via idempotence guards (re-check sigKey presence and hasStopped inside the record helpers). Do not treat the absence of a per-parent Effect.Mutex as a blocking review issue for this known edge case; the residual window should only create extra synthetic parts and must not change the "turn ends" contract. Respect the existing code comment documenting the trade-off, and only escalate to a full-mutex approach if the race is observed in practice.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `init` option in `tmpdir` to define custom setup functions that can return extra data accessible via `tmp.extra`, and use the `dispose` option for custom cleanup logic.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `tmpdir` function from `fixture/fixture.ts` to create temporary directories for tests with automatic cleanup. Use `await using` syntax to ensure automatic cleanup when the variable goes out of scope.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
📚 Learning: 2026-04-26T15:35:36.505Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 245
File: packages/opencode/src/question/index.ts:21-24
Timestamp: 2026-04-26T15:35:36.505Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/question/index.ts`), `Question.Option.description` is intentionally **required** (`z.string()`, not `.optional()`). This matches the upstream opencode contract and all current callers (e.g. `PlanExitTool`) always supply a description. The defensive `<Show when={props.description}>` rendering in `session-question-dock.tsx` is a standard guard, not a signal that the field is intended to be optional. Do NOT suggest making `Option.description` optional without a dedicated follow-up that covers schema + tool description + dock copy + tests.

Applied to files:

  • packages/opencode/src/tool/todo.ts
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.

Applied to files:

  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually

Applied to files:

  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/opencode/src/config/config.ts
🔇 Additional comments (22)
packages/opencode/src/tool/edit.ts (1)

149-160: Single-pass diff tally looks good.

Precomputing additions and deletions here matches the Snapshot.FileDiff shape and avoids the extra traversal without changing behavior.

packages/opencode/src/permission/index.ts (1)

287-312: Good: wildcard precedence is now normalized before evaluation.

This keeps the findLast()-based lookup predictable and preserves the intended “specific rules override wildcard fallbacks” behavior without changing nested rule order.

packages/opencode/src/util/effect-zod.ts (2)

43-53: Good: the object helper now validates its contract at runtime.

Using instanceof z.ZodType and instanceof z.ZodObject keeps this on public Zod APIs and avoids another private-property escape hatch.


63-89: Good: override, description, and ref metadata are composed cleanly.

The walker still applies outer metadata on top of the chosen base schema, and the check handling stays centralized instead of being duplicated across branches.

packages/opencode/src/project/schema.ts (1)

3-14: Good: ProjectID.zod now derives from the shared adapter.

This keeps the static in sync with the Effect schema and removes the local Zod construction path.

packages/opencode/test/util/effect-zod.test.ts (1)

5-765: Good coverage for the new adapter behavior.

The suite now exercises the important paths after the refactor: overrides, nested refs, transforms, defaults, refinement translation, and memoization. The dropped preprocess cases are no longer relevant now that support was removed.

packages/opencode/src/tool/schema.ts (1)

4-14: Good: ToolID now uses the shared Effect→Zod bridge.

This keeps the derived validator aligned with the Effect schema and preserves the override-based runtime pattern.

packages/opencode/src/pty/schema.ts (1)

4-14: Good: PtyID now follows the shared schema bridge pattern.

The branded Effect schema and its Zod accessor stay aligned, and the override mechanism remains intact.

packages/opencode/src/session/schema.ts (1)

4-31: Good: the session identifier schemas now share the same Zod derivation path.

SessionID, MessageID, and PartID all stay branded and keep their override-based runtime validators in sync with the Effect definitions.

packages/opencode/src/control-plane/schema.ts (1)

4-17: Looks good — the schema bridge stays consistent.

The ZodOverride annotation plus zod(schema) accessor matches the new Effect-first pattern, and the branded WorkspaceID type is preserved.

packages/opencode/src/question/schema.ts (1)

4-15: Looks good — this ID schema now derives from the Effect definition.

The ZodOverride annotation and zod(this) static keep the runtime and type-level validation in sync.

packages/opencode/src/sync/schema.ts (1)

4-11: Looks good — this keeps EventID sourced from the Effect schema.

The override annotation and zod(s) accessor are consistent with the new schema-generation flow.

packages/opencode/src/permission/schema.ts (1)

4-15: Looks good — PermissionID now derives from the canonical Effect schema.

The new annotation and shared zod(this) helper keep the validator aligned with the schema source.

packages/opencode/script/schema.ts (1)

56-56: Looks good — the generator now consumes the canonical config schema.

Using Config.Info.zod here keeps the emitted JSON schema in sync with the rest of the refactor.

packages/opencode/src/server/instance/config.ts (1)

26-27: Looks good — request/response contracts now share the same schema.

Using Config.Info.zod for both resolver(...) and validator(...) keeps the documented API and runtime validation aligned.

Also applies to: 47-48, 54-54

packages/opencode/test/session/compaction.test.ts (1)

177-180: Looks good — the test fixture now uses the Zod-backed config parser.

That keeps the mocked Config.Service aligned with the new schema source.

packages/opencode/src/config/agent.ts (1)

6-6: Looks good — the permission field now uses the concrete schema.

This keeps the agent config on the shared Effect schema path and removes the need for the old override placeholder.

Also applies to: 51-51

packages/opencode/src/server/instance/global.ts (1)

195-196: Consistent .zod migration for both OpenAPI and runtime validation.

Good update: docs (resolver) and request validator now point to the same Config.Info.zod schema source.

Also applies to: 216-217, 223-223

packages/opencode/test/config/config.test.ts (1)

2961-2963: parseManagedPlist schema calls are correctly aligned to Config.Info.zod.

Nice consistency update across all parseManagedPlist unit tests.

Also applies to: 2993-2995, 3013-3015, 3043-3045, 3060-3062

packages/opencode/test/permission/next.test.ts (1)

87-147: Great coverage for wildcard-vs-specific precedence invariants.

These additions lock in the key behavioral contract (* fallback, specific override, and preserved intra-key pattern order) very well.

packages/opencode/src/config/permission.ts (1)

54-77: Normalization + derived .zod wiring looks solid.

The InputSchema -> decodeTo(InputObject) path cleanly preserves shorthand support while keeping a canonical decoded shape for downstream consumers.

packages/opencode/src/config/config.ts (1)

148-156: Canonical schema + .zod compatibility migration is implemented consistently.

Strict derived Info.zod plus updated parse call sites are aligned and reduce split-schema drift risk.

Also applies to: 252-252, 298-305, 435-436, 851-852, 857-858

Comment thread packages/opencode/src/tool/todo.ts
Comment thread packages/opencode/src/config/permission.ts Outdated
@Astro-Han
Copy link
Copy Markdown
Owner Author

Outside-diff comment reply (test/session/session.test.ts 126-166)

Prevent Bus subscription leaks on failed assertions (use try/finally).

Deferring on scope. PR3 is refactor: migrate selected runtime schemas to Effect Schema; the only lines this PR touches in session.test.ts are 122-128 (a 3-line comment + the as MessageV2.Part cast — both required because the schema migration shifted bus payloads to readonly Schema.Type). The unsub() cleanup pattern at lines 126-166 is pre-existing PawWork test code, unmodified by this PR.

Wrapping the assertion block in try/finally is a sensible defensive change, but it has its own reversible intent (test-quality cleanup) and belongs in a separate commit per the repo's "commit small" rule. The leak is bounded — even on a failed assertion the subscription only persists until the test process exits, and await using tmp already disposes the per-test workspace — so the practical risk is low and not worth blocking this refactor on.

Will track the same-file inline nitpick (MessageV2.Part capture tightening) in the same follow-up.

@Astro-Han Astro-Han force-pushed the claude/upstream-sync-209-pr3-effect-schema branch from 205cdf4 to c04d805 Compare April 28, 2026 02:23
@Astro-Han
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Astro-Han
Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/opencode/test/config/config.test.ts (1)

2116-2135: 🛠️ Refactor suggestion | 🟠 Major

Use tmpdir({ config: ... }) for this fixture.

This case only needs an opencode.json, so the manual Filesystem.write(...) setup is extra plumbing and diverges from the repo fixture contract.

♻️ Suggested simplification
-  await using tmp = await tmpdir({
-    init: async (dir) => {
-      await Filesystem.write(
-        path.join(dir, "opencode.json"),
-        JSON.stringify({
-          $schema: "https://opencode.ai/config.json",
-          permission: {
-            "*": "deny",
-            edit: "ask",
-            write: "ask",
-            external_directory: "ask",
-            read: "allow",
-            todowrite: "allow",
-            "thoughts_*": "allow",
-            "reasoning_model_*": "allow",
-            "tools_*": "allow",
-            "pr_comments_*": "allow",
-          },
-        }),
-      )
-    },
-  })
+  await using tmp = await tmpdir({
+    config: {
+      permission: {
+        "*": "deny",
+        edit: "ask",
+        write: "ask",
+        external_directory: "ask",
+        read: "allow",
+        todowrite: "allow",
+        "thoughts_*": "allow",
+        "reasoning_model_*": "allow",
+        "tools_*": "allow",
+        "pr_comments_*": "allow",
+      },
+    },
+  })

As per coding guidelines, "Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/config/config.test.ts` around lines 2116 - 2135, The
test is manually writing opencode.json with Filesystem.write inside the tmpdir
init; instead use the tmpdir helper's config option to create the config file.
Replace the tmpdir({ init: async (dir) => { Filesystem.write(... "opencode.json"
...) } }) usage with tmpdir({ config: <partial Config.Info object matching the
JSON shown> }) so the opencode.json is created by tmpdir; update the test to
pass the same permission object via the config parameter and remove the
Filesystem.write call and the init function. Ensure you modify the invocation
named tmpdir and remove reliance on Filesystem.write.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/permission/index.ts`:
- Around line 294-303: The loop that builds ruleset lets a legacy key (e.g.,
rawKey "task") mapped via LEGACY_KEY_ALIASES to the canonical key ("agent")
override an earlier explicit canonical entry; to fix, after resolving key =
LEGACY_KEY_ALIASES[rawKey] ?? rawKey, detect legacy-to-canonical cases (rawKey
!== key) and skip adding the legacy entry when the original permission object
already contains the canonical key (use
Object.prototype.hasOwnProperty.call(permission, key) or similar); update the
for (const [rawKey, value] of entries) block so legacy aliases are ignored when
a canonical property exists, preserving canonical precedence in
ruleset/evaluate()/disabled().

In `@packages/opencode/src/util/effect-zod.ts`:
- Around line 65-72: The current logic uses `override ?? bodyWithChecks(ast)`
which skips applying `ast.checks` when `override` is present; change this so
checks are always layered onto the chosen schema: if `override` exists call the
check-applying helper (the same logic used by `bodyWithChecks`/`applyChecks`)
with `override` and `ast.checks`, otherwise use `bodyWithChecks(ast)`; keep the
subsequent use of `SchemaAST.resolveDescription(ast)` and
`SchemaAST.resolveIdentifier(ast)` and apply `describe` and `meta({ ref })` to
the result so description/ref metadata remains unchanged.

---

Outside diff comments:
In `@packages/opencode/test/config/config.test.ts`:
- Around line 2116-2135: The test is manually writing opencode.json with
Filesystem.write inside the tmpdir init; instead use the tmpdir helper's config
option to create the config file. Replace the tmpdir({ init: async (dir) => {
Filesystem.write(... "opencode.json" ...) } }) usage with tmpdir({ config:
<partial Config.Info object matching the JSON shown> }) so the opencode.json is
created by tmpdir; update the test to pass the same permission object via the
config parameter and remove the Filesystem.write call and the init function.
Ensure you modify the invocation named tmpdir and remove reliance on
Filesystem.write.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 347ea3b9-dba7-48b8-b147-66347b2ddbe1

📥 Commits

Reviewing files that changed from the base of the PR and between ae1e1d7 and c04d805.

⛔ Files ignored due to path filters (1)
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (23)
  • packages/opencode/script/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
🧠 Learnings (47)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/tool/todo.ts:7-22
Timestamp: 2026-04-28T02:24:27.738Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork, PR `#280`), the `parameters` constant for `TodoWriteTool` intentionally duplicates the `Todo.Info` fields inline rather than referencing `Todo.Info` or `zodObject(Todo.Info)`. After the Effect Schema migration in this PR, `Todo.Info` is an Effect Schema, so `zodObject(Todo.Info)` returns `z.ZodObject<any>` — which erases per-field types that `Tool.define`'s `execute()` inference depends on. The inline Zod schema is a deliberate temporary measure; tool schemas will migrate to Effect Schema as a separate slice per `specs/effect/schema.md`. Do NOT suggest replacing the inline schema with `z.array(Todo.Info)` or `zodObject(Todo.Info)` until that migration lands.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.
📚 Learning: 2026-04-28T02:24:27.738Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/tool/todo.ts:7-22
Timestamp: 2026-04-28T02:24:27.738Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork, PR `#280`), the `parameters` constant for `TodoWriteTool` intentionally duplicates the `Todo.Info` fields inline rather than referencing `Todo.Info` or `zodObject(Todo.Info)`. After the Effect Schema migration in this PR, `Todo.Info` is an Effect Schema, so `zodObject(Todo.Info)` returns `z.ZodObject<any>` — which erases per-field types that `Tool.define`'s `execute()` inference depends on. The inline Zod schema is a deliberate temporary measure; tool schemas will migrate to Effect Schema as a separate slice per `specs/effect/schema.md`. Do NOT suggest replacing the inline schema with `z.array(Todo.Info)` or `zodObject(Todo.Info)` until that migration lands.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-28T01:24:29.645Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-21T16:57:23.748Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/drizzle.config.ts : Configure Drizzle Kit migrations with schema path `./src/**/*.sql.ts` and output path `./migration`

Applied to files:

  • packages/opencode/script/schema.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Class` for multi-field data in Effect schemas

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-27T11:18:47.596Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/agent/agent.test.ts:440-447
Timestamp: 2026-04-27T11:18:47.596Z
Learning: In `packages/opencode/test/agent/agent.test.ts` (Astro-Han/pawwork), all agent-permission tests intentionally use the manual `tmpdir()` + `Instance.provide(...)` pattern. Do not flag individual tests in this file for conversion to `provideTmpdirInstance(...)` or `provideInstance(...)`; a full harness migration would be a separate PR if the pattern ever needs to change.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-27T11:18:47.332Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `tmpdir` function from `fixture/fixture.ts` to create temporary directories for tests with automatic cleanup. Use `await using` syntax to ensure automatic cleanup when the variable goes out of scope.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-27T12:59:45.694Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:45.694Z
Learning: In session prompt/diagnostics tests (e.g., when verifying injected LLM “recovery reminder” copy), do not assert a single exact reminder phrasing tied to signature kind. If a scenario can produce both `input:` and `target:` signatures (for example, a `read` tool call with a `filePath` parameter), accept either phrasing: the input-repeat variant may say “repeated the same tool input 3 times” (literal count), and the target-repeat variant may say “failed against the same target multiple times” (“multiple times” without a count). Your assertions should allow both variants rather than narrowing to only the input-variant text.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-27T10:33:12.228Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-24T06:50:05.142Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T10:08:57.448Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/diagnostics.test.ts:313-318
Timestamp: 2026-04-27T10:08:57.448Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `SessionDiagnostics.LoopAction` is a TypeScript string literal union type (`"observe" | "block" | "stop"`), NOT a runtime enum. There is no runtime form to introspect via `Object.values()`. The test pattern `const all: SessionDiagnostics.LoopAction[] = ["observe", "block", "stop"]; expect(all).toHaveLength(3)` is intentional and correct — it provides compile-time exhaustiveness via the type annotation. Do NOT suggest replacing this with `Object.values()` introspection or flag the runtime length check as insufficient.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-25T12:52:39.403Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/session/export.ts:75-88
Timestamp: 2026-04-25T12:52:39.403Z
Learning: In `packages/opencode/src/session/export.ts` (Astro-Han/pawwork), the `extractReasonFromCause` function intentionally uses a lightweight cast-based inspection of the Cause `reasons` array instead of `Cause.failures()` / `Cause.defects()` utilities. This is a deliberate choice: the function is diagnostic-only (used to populate a best-effort reason string for export diagnostics), and the maintainer explicitly prefers not to add coupling to Effect's Cause API surface for this purpose. Do not flag this pattern as fragile or suggest replacing it with Cause utilities.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T08:27:43.672Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:255-279
Timestamp: 2026-04-27T08:27:43.672Z
Learning: In `packages/opencode/src/session/diagnostics.ts` (PR `#264`, issue `#229`), target-level loop detection is intentionally tool-scoped. The `real` filter includes `r.tool === input.tool` and target signature keys are formatted as `target:${tool}:${targetHash}`. Cross-tool retries on the same URL/path/query are considered legitimate exploration (e.g., webfetch failed → switch to a different tool), NOT a stuck loop. Only same-tool + same-target accumulation counts toward block/stop. Do NOT suggest removing the tool scope from target signature keys or the `real` filter.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T11:19:24.963Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T10:33:02.677Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:33:02.677Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `loopLastError` is stored as raw (unscrubbed) error text in loop metadata intentionally. The trust boundary for sensitive data scrubbing is the renderer (`LoopRenderer.render` already applies `scrubErrorText` before any user/model-facing output), not the storage layer. Failed tool parts also already store raw errors in `state.error` (PR `#204` default behavior), so scrubbing only `loopLastError` would not close that pre-existing leak and would add lossy storage. If export-side hardening is needed, the correct approach is a single sanitizer pass over both `state.error` and metadata in the export layer. Do NOT suggest scrubbing `loopLastError` at the `observeToolError` call site.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-24T03:51:56.211Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:56.211Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.TaggedErrorClass` for typed errors in Effect schemas

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-26T15:35:36.505Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 245
File: packages/opencode/src/question/index.ts:21-24
Timestamp: 2026-04-26T15:35:36.505Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/question/index.ts`), `Question.Option.description` is intentionally **required** (`z.string()`, not `.optional()`). This matches the upstream opencode contract and all current callers (e.g. `PlanExitTool`) always supply a description. The defensive `<Show when={props.description}>` rendering in `session-question-dock.tsx` is a standard guard, not a signal that the field is intended to be optional. Do NOT suggest making `Option.description` optional without a dedicated follow-up that covers schema + tool description + dock copy + tests.

Applied to files:

  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.

Applied to files:

  • packages/opencode/src/tool/todo.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches

Applied to files:

  • packages/opencode/src/tool/todo.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` services when those concerns are already inside Effect code

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O in Effect services

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.fn("Domain.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer `.pipe()` wrappers

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-21T16:57:20.257Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:20.257Z
Learning: In packages/opencode/src/config/ config modules, treat both `Schema.Struct` and `Schema.Class` as valid schema patterns and do not require converting `Schema.Struct` to `Schema.Class`. If `Schema.Struct` is used in a config schema (e.g., in files like `command.ts`, `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`), accept it as intentional; the selection should be based on upstream alignment and the local schema context, not on a rule that one form must replace the other.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-27T10:32:59.274Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:32:59.274Z
Learning: In pawwork’s session diagnostics/loop error handling (e.g., packages/opencode/src/session/diagnostics.ts), keep `loopLastError` stored as raw/unscrubbed error text in loop metadata. The intended trust boundary for sensitive-data scrubbing is the renderer: `LoopRenderer.render` must apply `scrubErrorText` before any user/model-facing output. Do not recommend scrubbing `loopLastError` at the `observeToolError` call site, because failed tool parts already store raw errors in `state.error` (pre-existing leak) and scrubbing only `loopLastError` would be lossy without closing the gap. If export-side hardening is required, implement a single sanitizer pass in the export layer over both `state.error` and the loop/metadata fields, rather than sanitizing only `loopLastError` earlier in the storage path.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-27T10:33:08.974Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:08.974Z
Learning: For Astro-Han/pawwork session processing code under packages/opencode/src/session/ (e.g., prompt.ts and processor.ts), this specific loop-gate TOCTOU/race between buildLoopContext() and recordSyntheticBlock/recordSyntheticStop is intentionally handled via idempotence guards (re-check sigKey presence and hasStopped inside the record helpers). Do not treat the absence of a per-parent Effect.Mutex as a blocking review issue for this known edge case; the residual window should only create extra synthetic parts and must not change the "turn ends" contract. Respect the existing code comment documenting the trade-off, and only escalate to a full-mutex approach if the race is observed in practice.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.

Applied to files:

  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-24T17:08:46.780Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually

Applied to files:

  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/opencode/src/config/config.ts

Comment thread packages/opencode/src/permission/index.ts
Comment thread packages/opencode/src/util/effect-zod.ts
@Astro-Han Astro-Han force-pushed the claude/upstream-sync-209-pr3-effect-schema branch from c04d805 to ebd1167 Compare April 28, 2026 03:31
@Astro-Han
Copy link
Copy Markdown
Owner Author

Outside-diff comment reply (test/config/config.test.ts 2116-2135)

Use tmpdir({ config: ... }) for this fixture.

Pushing back on scope. The tmpdir({ init: async (dir) => Filesystem.write(...) }) pattern is the file-wide convention in config.test.tsgrep finds 50+ tests using it and only one (line 108) using tmpdir({ config: ... }). The test in question (permission config canonicalises known keys first, preserves rest-key insertion order) inherits the init: form from upstream's original permission config preserves key order test; PR3 only renamed it and updated the assertion to reflect the new StructWithRest canonical order, so the manual-write block is pre-existing PawWork test code unmodified by this PR.

The test/AGENTS.md guideline does prefer config: for the simple case, and aligning to it is correct — but as a sweep across all 50+ call sites, not piecemeal on one test in the middle of a schema-migration refactor. Mixing test-fixture cleanup into this PR would dilute its commit intent and leave the file in an inconsistent state. Will track separately.

Squashes upstream PRs #23716, #23740, #23744, #23745, #23747, #23749,
#23752, #23754, #23756, #23757, #23763, #24005, #24040, #24169, #24213
into a single PawWork commit. Ranges over 16 upstream commits in
date order from upstream's Effect Schema migration batch.

What landed:
- Config.Info, ConfigPermission.Info, ConfigAgent.Info schema definitions
  migrated to Effect Schema canonical form. PawWork callers use
  Config.Info.zod accessor where Hono validators or Zod-style decoders
  are still needed.
- Schema definition files in control-plane, permission, project, pty,
  question, sync, tool, session migrated to Effect Schema.
- packages/opencode/src/util/effect-zod.ts walker enhanced to derive
  .zod from Effect Schema at runtime, plus tests.
- packages/sdk/js/v2/gen/types.gen.ts regenerated to reflect schema
  shape changes.

What was preserved (PawWork divergence kept on Zod / namespace API):
- packages/opencode/src/session/{session,message-v2,prompt,export,message,revert,summary,todo,status,compaction,projectors}.ts
  — PawWork's session domain stays Zod-shaped to keep MessageV2
  PawWork-only types (FileSource/SymbolSource/ResourceSource carry
  PawWork attachment metadata) compatible with current callers.
- packages/opencode/src/bus/bus-event.ts — PawWork keeps Zod-typed
  BusEvent.define so the 30+ PawWork test fixtures and divergent
  consumers (server/instance/*, acp/agent.ts, file/watcher.ts) keep
  reading evt.properties as their schema's inferred type rather than
  unknown.
- packages/opencode/src/{snapshot,permission,mcp,provider}/index.ts —
  these modules use a PawWork "namespace + XValue alias" pattern that
  upstream redesigned. Keeping HEAD here preserves Snapshot.track,
  Permission.ask, Mcp.add, Provider.list etc. for PawWork callers.
- packages/opencode/src/provider/* — PawWork's volcengine/zen
  integration carries divergent code paths (VOLCENGINE_PLAN constants,
  HTTP-Referer/X-Title carve-out per project_rename_blockers.md).
- packages/opencode/src/{file,project,pty,sync,ide,lsp/client,command,
  control-plane/workspace,worktree,question,util/schema}/{index,*}.ts
  — kept PawWork API surface for callers.

Skipped commits in this range (no PawWork-applicable change after
carve-outs): 93940a1859 (provider domain) — provider.ts is a PawWork
permanent carve-out; the migration is reapplied to PawWork's local
provider only when divergence settles.

Verified: typecheck clean across packages/{core, opencode, util, ui,
desktop-electron}.

Refs: #209
@Astro-Han Astro-Han force-pushed the claude/upstream-sync-209-pr3-effect-schema branch from ebd1167 to 42557db Compare April 28, 2026 03:38
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/opencode/test/session/session.test.ts (1)

126-129: ⚠️ Potential issue | 🟡 Minor

Narrow the captured bus payload to step-finish in the subscriber.

At Line [128], the handler stores every PartUpdated variant into received; a non-step-finish event can overwrite it and make this assertion path flaky.

🔧 Suggested fix
-          let received: MessageV2.Part | undefined
+          let received: MessageV2.StepFinishPart | undefined
           const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, (event) => {
-            received = event.properties.part as MessageV2.Part
+            const part = event.properties.part
+            if (part.type !== "step-finish") return
+            received = part as MessageV2.StepFinishPart
           })
@@
-          expect(received!.type).toBe("step-finish")
-          const finish = received as MessageV2.StepFinishPart
+          const finish = received!

Also applies to: 152-155

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/test/session/session.test.ts` around lines 126 - 129, The
subscriber for MessageV2.Event.PartUpdated is currently assigning every variant
into the shared received variable, which allows non-`step-finish` parts to
overwrite it; update the handler registered with
Bus.subscribe(MessageV2.Event.PartUpdated, ...) used in the test (the received
variable and unsub closure) to guard on the part being the `step-finish` variant
(check the event payload property that denotes variant/kind, e.g.,
event.properties.part.kind or event.properties.part.event === 'step-finish') and
only set received when that guard passes; apply the same narrowing change to the
other subscriber instance referenced at lines 152-155.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/opencode/src/config/config.ts`:
- Around line 148-155: The exported Config.Info was changed to an Effect schema
which removes direct Zod methods (breaking callers); restore a thin
compatibility shim by keeping the new Effect schema (e.g., ConfigInfo) but
re-exporting a namespace-compatible surface named Config.Info that exposes the
original Zod-style API (at minimum a .zod reference to the Effect-derived Zod
schema and a parse alias), and also re-export Config.parse = Config.Info.parse
if parse was previously part of the public namespace; update the symbol
references around Config.Info / ConfigInfo to return this shim so existing
callers keep working.

---

Duplicate comments:
In `@packages/opencode/test/session/session.test.ts`:
- Around line 126-129: The subscriber for MessageV2.Event.PartUpdated is
currently assigning every variant into the shared received variable, which
allows non-`step-finish` parts to overwrite it; update the handler registered
with Bus.subscribe(MessageV2.Event.PartUpdated, ...) used in the test (the
received variable and unsub closure) to guard on the part being the
`step-finish` variant (check the event payload property that denotes
variant/kind, e.g., event.properties.part.kind or event.properties.part.event
=== 'step-finish') and only set received when that guard passes; apply the same
narrowing change to the other subscriber instance referenced at lines 152-155.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f091ab74-dd17-4536-a452-cade18d46e57

📥 Commits

Reviewing files that changed from the base of the PR and between c04d805 and ebd1167.

⛔ Files ignored due to path filters (1)
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (23)
  • packages/opencode/script/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-app
  • GitHub Check: unit-opencode
  • GitHub Check: typecheck
  • GitHub Check: unit-desktop
  • GitHub Check: analyze-js-ts
  • GitHub Check: e2e-artifacts
🧰 Additional context used
📓 Path-based instructions (2)
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/test/config/config.test.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
🧠 Learnings (46)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/tool/todo.ts:7-22
Timestamp: 2026-04-28T02:24:27.738Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork, PR `#280`), the `parameters` constant for `TodoWriteTool` intentionally duplicates the `Todo.Info` fields inline rather than referencing `Todo.Info` or `zodObject(Todo.Info)`. After the Effect Schema migration in this PR, `Todo.Info` is an Effect Schema, so `zodObject(Todo.Info)` returns `z.ZodObject<any>` — which erases per-field types that `Tool.define`'s `execute()` inference depends on. The inline Zod schema is a deliberate temporary measure; tool schemas will migrate to Effect Schema as a separate slice per `specs/effect/schema.md`. Do NOT suggest replacing the inline schema with `z.array(Todo.Info)` or `zodObject(Todo.Info)` until that migration lands.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.
📚 Learning: 2026-04-28T02:24:27.738Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/tool/todo.ts:7-22
Timestamp: 2026-04-28T02:24:27.738Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork, PR `#280`), the `parameters` constant for `TodoWriteTool` intentionally duplicates the `Todo.Info` fields inline rather than referencing `Todo.Info` or `zodObject(Todo.Info)`. After the Effect Schema migration in this PR, `Todo.Info` is an Effect Schema, so `zodObject(Todo.Info)` returns `z.ZodObject<any>` — which erases per-field types that `Tool.define`'s `execute()` inference depends on. The inline Zod schema is a deliberate temporary measure; tool schemas will migrate to Effect Schema as a separate slice per `specs/effect/schema.md`. Do NOT suggest replacing the inline schema with `z.array(Todo.Info)` or `zodObject(Todo.Info)` until that migration lands.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-28T01:24:29.645Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-21T16:57:23.748Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/drizzle.config.ts : Configure Drizzle Kit migrations with schema path `./src/**/*.sql.ts` and output path `./migration`

Applied to files:

  • packages/opencode/script/schema.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T10:33:12.228Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T12:59:45.694Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:45.694Z
Learning: In session prompt/diagnostics tests (e.g., when verifying injected LLM “recovery reminder” copy), do not assert a single exact reminder phrasing tied to signature kind. If a scenario can produce both `input:` and `target:` signatures (for example, a `read` tool call with a `filePath` parameter), accept either phrasing: the input-repeat variant may say “repeated the same tool input 3 times” (literal count), and the target-repeat variant may say “failed against the same target multiple times” (“multiple times” without a count). Your assertions should allow both variants rather than narrowing to only the input-variant text.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/session/compaction.test.ts
📚 Learning: 2026-04-24T06:50:05.142Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T10:08:57.448Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/diagnostics.test.ts:313-318
Timestamp: 2026-04-27T10:08:57.448Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `SessionDiagnostics.LoopAction` is a TypeScript string literal union type (`"observe" | "block" | "stop"`), NOT a runtime enum. There is no runtime form to introspect via `Object.values()`. The test pattern `const all: SessionDiagnostics.LoopAction[] = ["observe", "block", "stop"]; expect(all).toHaveLength(3)` is intentional and correct — it provides compile-time exhaustiveness via the type annotation. Do NOT suggest replacing this with `Object.values()` introspection or flag the runtime length check as insufficient.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-25T12:52:39.403Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/session/export.ts:75-88
Timestamp: 2026-04-25T12:52:39.403Z
Learning: In `packages/opencode/src/session/export.ts` (Astro-Han/pawwork), the `extractReasonFromCause` function intentionally uses a lightweight cast-based inspection of the Cause `reasons` array instead of `Cause.failures()` / `Cause.defects()` utilities. This is a deliberate choice: the function is diagnostic-only (used to populate a best-effort reason string for export diagnostics), and the maintainer explicitly prefers not to add coupling to Effect's Cause API surface for this purpose. Do not flag this pattern as fragile or suggest replacing it with Cause utilities.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T08:27:43.672Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:255-279
Timestamp: 2026-04-27T08:27:43.672Z
Learning: In `packages/opencode/src/session/diagnostics.ts` (PR `#264`, issue `#229`), target-level loop detection is intentionally tool-scoped. The `real` filter includes `r.tool === input.tool` and target signature keys are formatted as `target:${tool}:${targetHash}`. Cross-tool retries on the same URL/path/query are considered legitimate exploration (e.g., webfetch failed → switch to a different tool), NOT a stuck loop. Only same-tool + same-target accumulation counts toward block/stop. Do NOT suggest removing the tool scope from target signature keys or the `real` filter.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T11:18:47.332Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-27T11:19:24.963Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-27T10:33:02.677Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:33:02.677Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `loopLastError` is stored as raw (unscrubbed) error text in loop metadata intentionally. The trust boundary for sensitive data scrubbing is the renderer (`LoopRenderer.render` already applies `scrubErrorText` before any user/model-facing output), not the storage layer. Failed tool parts also already store raw errors in `state.error` (PR `#204` default behavior), so scrubbing only `loopLastError` would not close that pre-existing leak and would add lossy storage. If export-side hardening is needed, the correct approach is a single sanitizer pass over both `state.error` and metadata in the export layer. Do NOT suggest scrubbing `loopLastError` at the `observeToolError` call site.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T11:18:47.596Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/agent/agent.test.ts:440-447
Timestamp: 2026-04-27T11:18:47.596Z
Learning: In `packages/opencode/test/agent/agent.test.ts` (Astro-Han/pawwork), all agent-permission tests intentionally use the manual `tmpdir()` + `Instance.provide(...)` pattern. Do not flag individual tests in this file for conversion to `provideTmpdirInstance(...)` or `provideInstance(...)`; a full harness migration would be a separate PR if the pattern ever needs to change.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-24T03:51:56.211Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:56.211Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Class` for multi-field data in Effect schemas

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.TaggedErrorClass` for typed errors in Effect schemas

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-21T16:57:20.257Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:20.257Z
Learning: In packages/opencode/src/config/ config modules, treat both `Schema.Struct` and `Schema.Class` as valid schema patterns and do not require converting `Schema.Struct` to `Schema.Class`. If `Schema.Struct` is used in a config schema (e.g., in files like `command.ts`, `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`), accept it as intentional; the selection should be based on upstream alignment and the local schema context, not on a rule that one form must replace the other.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` services when those concerns are already inside Effect code

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O in Effect services

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.fn("Domain.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer `.pipe()` wrappers

Applied to files:

  • packages/opencode/src/config/agent.ts
📚 Learning: 2026-04-26T15:35:36.505Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 245
File: packages/opencode/src/question/index.ts:21-24
Timestamp: 2026-04-26T15:35:36.505Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/question/index.ts`), `Question.Option.description` is intentionally **required** (`z.string()`, not `.optional()`). This matches the upstream opencode contract and all current callers (e.g. `PlanExitTool`) always supply a description. The defensive `<Show when={props.description}>` rendering in `session-question-dock.tsx` is a standard guard, not a signal that the field is intended to be optional. Do NOT suggest making `Option.description` optional without a dedicated follow-up that covers schema + tool description + dock copy + tests.

Applied to files:

  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.

Applied to files:

  • packages/opencode/src/tool/todo.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches

Applied to files:

  • packages/opencode/src/tool/todo.ts
📚 Learning: 2026-04-27T10:32:59.274Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:32:59.274Z
Learning: In pawwork’s session diagnostics/loop error handling (e.g., packages/opencode/src/session/diagnostics.ts), keep `loopLastError` stored as raw/unscrubbed error text in loop metadata. The intended trust boundary for sensitive-data scrubbing is the renderer: `LoopRenderer.render` must apply `scrubErrorText` before any user/model-facing output. Do not recommend scrubbing `loopLastError` at the `observeToolError` call site, because failed tool parts already store raw errors in `state.error` (pre-existing leak) and scrubbing only `loopLastError` would be lossy without closing the gap. If export-side hardening is required, implement a single sanitizer pass in the export layer over both `state.error` and the loop/metadata fields, rather than sanitizing only `loopLastError` earlier in the storage path.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-27T10:33:08.974Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:08.974Z
Learning: For Astro-Han/pawwork session processing code under packages/opencode/src/session/ (e.g., prompt.ts and processor.ts), this specific loop-gate TOCTOU/race between buildLoopContext() and recordSyntheticBlock/recordSyntheticStop is intentionally handled via idempotence guards (re-check sigKey presence and hasStopped inside the record helpers). Do not treat the absence of a per-parent Effect.Mutex as a blocking review issue for this known edge case; the residual window should only create extra synthetic parts and must not change the "turn ends" contract. Respect the existing code comment documenting the trade-off, and only escalate to a full-mutex approach if the race is observed in practice.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually

Applied to files:

  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.

Applied to files:

  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-24T17:08:46.780Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/opencode/src/config/permission.ts

Comment thread packages/opencode/src/config/config.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/opencode/src/config/config.ts (1)

148-155: ⚠️ Potential issue | 🟠 Major

Restore the direct parse surface on Config.Info.

Info now exposes only .zod, which drops the previous Config.Info.parse(...) / Config.parse(...) API from the exported namespace. If this migration is meant to stay namespace-compatible, bind parse/safeParse off the derived Zod object here instead of forcing downstream renames.

♻️ Compatibility shim
-import { zod, ZodOverride } from "@/util/effect-zod"
+import { zodObject, ZodOverride } from "@/util/effect-zod"
...
 .annotate({ identifier: "Config" })
 .pipe(
-  withStatics((s) => ({
-    zod: (zod(s) as unknown as z.ZodObject<any>)
-      .strict()
-      .meta({ ref: "Config" }) as unknown as z.ZodType<DeepMutable<Schema.Schema.Type<typeof s>>>,
-  })),
+  withStatics((s) => {
+    const schema = zodObject(s).strict().meta({ ref: "Config" })
+    return {
+      zod: schema as unknown as z.ZodType<DeepMutable<Schema.Schema.Type<typeof s>>>,
+      parse: schema.parse.bind(schema),
+      safeParse: schema.safeParse.bind(schema),
+    }
+  }),
 )
 export namespace Config {
   export const Info = ConfigInfo
+  export const parse = ConfigInfo.parse

Also applies to: 299-304

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opencode/src/config/config.ts` around lines 148 - 155, The exported
Config.Info currently exposes only .zod and lost the previous
Config.Info.parse/safeParse (and Config.parse) namespace API; restore
compatibility by binding parse and safeParse from the derived Zod object to the
Config.Info surface (e.g., set Config.Info.parse = Config.Info.zod.parse and
Config.Info.safeParse = Config.Info.zod.safeParse or similarly attach
parse/safeParse to the exported Config object) so callers can continue using
Config.Info.parse(...) and Config.parse(...); ensure the same shim is applied in
the other mentioned block (lines referenced 299-304) to keep namespace
compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/opencode/src/config/config.ts`:
- Around line 148-155: The exported Config.Info currently exposes only .zod and
lost the previous Config.Info.parse/safeParse (and Config.parse) namespace API;
restore compatibility by binding parse and safeParse from the derived Zod object
to the Config.Info surface (e.g., set Config.Info.parse = Config.Info.zod.parse
and Config.Info.safeParse = Config.Info.zod.safeParse or similarly attach
parse/safeParse to the exported Config object) so callers can continue using
Config.Info.parse(...) and Config.parse(...); ensure the same shim is applied in
the other mentioned block (lines referenced 299-304) to keep namespace
compatibility.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dd0806c0-7f32-46fc-a49c-5894a454d317

📥 Commits

Reviewing files that changed from the base of the PR and between ebd1167 and 42557db.

⛔ Files ignored due to path filters (1)
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (24)
  • packages/opencode/script/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: unit-windows-opencode-config-project
  • GitHub Check: unit-windows-opencode-server-tools
  • GitHub Check: unit-windows-opencode-session
  • GitHub Check: unit-windows-desktop
  • GitHub Check: unit-windows-app
  • GitHub Check: unit-desktop
  • GitHub Check: unit-opencode
  • GitHub Check: smoke-macos-arm64
  • GitHub Check: e2e-artifacts
  • GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (2)
packages/opencode/**/*.ts

📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)

packages/opencode/**/*.ts: Use Effect.gen(function* () { ... }) for Effect composition
Use Effect.fn("Domain.method") for named/traced effects and Effect.fnUntraced for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer .pipe() wrappers
Use Effect.callback for callback-based APIs
Prefer DateTime.nowAsDate over new Date(yield* Clock.currentTimeMillis) when you need a Date in Effect code
Use Schema.Class for multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
Use Schema.TaggedErrorClass for typed errors in Effect schemas
Use Schema.Defect instead of unknown for defect-like causes in Effect code
In Effect.gen / Effect.fn, prefer yield* new MyError(...) over yield* Effect.fail(new MyError(...)) for direct early-failure branches
Use makeRuntime from src/effect/run-service.ts for all services; it returns { runPromise, runFork, runCallback } backed by a shared memoMap that deduplicates layers
Use InstanceState from src/effect/instance-state.ts for per-directory or per-project state that needs per-instance cleanup; do work directly in the InstanceState.make closure where ScopedCache handles run-once semantics
Use Effect.addFinalizer or Effect.acquireRelease inside the InstanceState.make closure for cleanup (subscriptions, process teardown, etc.)
Use Effect.forkScoped inside the InstanceState.make closure for background stream consumers — the fiber is interrupted when the instance is disposed
Prefer FileSystem.FileSystem instead of raw fs/promises for effectful file I/O in Effect services
Prefer ChildProcessSpawner.ChildProcessSpawner with ChildProcess.make(...) instead of custom process wrappers in Effect services
Prefer HttpClient.HttpClient instead of raw fetch in Effect services
Prefer Path.Path, Config, Clock, and DateTime services when those concerns are already inside Effect code
For backgroun...

Files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/src/config/permission.ts
packages/opencode/test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)

packages/opencode/test/**/*.test.{ts,tsx}: Use the tmpdir function from fixture/fixture.ts to create temporary directories for tests with automatic cleanup. Use await using syntax to ensure automatic cleanup when the variable goes out of scope.
When using the tmpdir function with git repository support, pass the git: true option to initialize a git repo with a root commit.
Use the config option in tmpdir to write an opencode.json config file during test setup by passing a partial Config.Info object.
Use the init option in tmpdir to define custom setup functions that can return extra data accessible via tmp.extra, and use the dispose option for custom cleanup logic.
Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows.
Use it.effect(...) when the test should run with TestClock and TestConsole. Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers from fixture/fixture.ts over building manual runtimes in tests: use tmpdirScoped() for scoped temp directories, provideInstance(dir)(effect) for low-level binding without directory creation, provideTmpdirInstance(...) for single temp instance binding, or provideTmpdirServer(...) for tests that also need the test LLM server.
Define const it = testEffect(...) near the top of the test file and keep the test body inside Effect.gen(function* () { ... }). Yield services directly with yield* MyService.Service or yield* MyTool.
Avoid custom ManagedRuntime, attach(...), or ad hoc run(...) wrappers in Effect tests when testEffect(...) already provides the runtime.
When a test needs instance-local state, prefer provideTmpdirInstance(...) or provideInstance(...) over manual Instance.provide(...) inside Promise-style tests.

Files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
🧠 Learnings (46)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/tool/todo.ts:7-22
Timestamp: 2026-04-28T02:24:27.738Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork, PR `#280`), the `parameters` constant for `TodoWriteTool` intentionally duplicates the `Todo.Info` fields inline rather than referencing `Todo.Info` or `zodObject(Todo.Info)`. After the Effect Schema migration in this PR, `Todo.Info` is an Effect Schema, so `zodObject(Todo.Info)` returns `z.ZodObject<any>` — which erases per-field types that `Tool.define`'s `execute()` inference depends on. The inline Zod schema is a deliberate temporary measure; tool schemas will migrate to Effect Schema as a separate slice per `specs/effect/schema.md`. Do NOT suggest replacing the inline schema with `z.array(Todo.Info)` or `zodObject(Todo.Info)` until that migration lands.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.
📚 Learning: 2026-04-28T02:24:27.738Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/tool/todo.ts:7-22
Timestamp: 2026-04-28T02:24:27.738Z
Learning: In `packages/opencode/src/tool/todo.ts` (Astro-Han/pawwork, PR `#280`), the `parameters` constant for `TodoWriteTool` intentionally duplicates the `Todo.Info` fields inline rather than referencing `Todo.Info` or `zodObject(Todo.Info)`. After the Effect Schema migration in this PR, `Todo.Info` is an Effect Schema, so `zodObject(Todo.Info)` returns `z.ZodObject<any>` — which erases per-field types that `Tool.define`'s `execute()` inference depends on. The inline Zod schema is a deliberate temporary measure; tool schemas will migrate to Effect Schema as a separate slice per `specs/effect/schema.md`. Do NOT suggest replacing the inline schema with `z.array(Todo.Info)` or `zodObject(Todo.Info)` until that migration lands.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-28T01:24:29.645Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 280
File: packages/opencode/src/session/projectors.ts:0-0
Timestamp: 2026-04-28T01:24:29.645Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/projectors.ts`), `SyncEvent.Definition.schema` preserves the literal Zod type through `z.infer<Def["schema"]>`, so `data.info` inside a `SyncEvent.project(Session.Event.Created, ...)` callback is already typed as `Session.Info` by the TypeScript compiler. No `as Session.Info` cast or `Session.Info.parse()` call is needed at this persistence boundary — a typecheck-clean removal of any such cast is sufficient.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use branded schemas (`Schema.brand`) for single-value types in Effect

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-21T16:57:23.748Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:23.748Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/config/`), `Schema.Struct` and `Schema.Class` are both valid and intentionally used patterns across config modules (e.g. `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`, `command.ts`). Do not flag `Schema.Struct` usage in config schemas as needing conversion to `Schema.Class` — the choice is driven by upstream alignment and local context, not inconsistency.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.

Applied to files:

  • packages/opencode/script/schema.ts
  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/drizzle.config.ts : Configure Drizzle Kit migrations with schema path `./src/**/*.sql.ts` and output path `./migration`

Applied to files:

  • packages/opencode/script/schema.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/test/config/config.test.ts
📚 Learning: 2026-04-27T12:59:45.694Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:45.694Z
Learning: In session prompt/diagnostics tests (e.g., when verifying injected LLM “recovery reminder” copy), do not assert a single exact reminder phrasing tied to signature kind. If a scenario can produce both `input:` and `target:` signatures (for example, a `read` tool call with a `filePath` parameter), accept either phrasing: the input-repeat variant may say “repeated the same tool input 3 times” (literal count), and the target-repeat variant may say “failed against the same target multiple times” (“multiple times” without a count). Your assertions should allow both variants rather than narrowing to only the input-variant text.

Applied to files:

  • packages/opencode/test/session/compaction.test.ts
  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-26T16:34:57.130Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 247
File: packages/ui/src/components/message-part.tsx:1322-1324
Timestamp: 2026-04-26T16:34:57.130Z
Learning: In Astro-Han/pawwork (`packages/ui/src/components/message-part.tsx`), the `taskId` createMemo and `childSessionId` createMemo both intentionally read only from `partMetadata().sessionId` (populated post-execution), not from `input.task_id` / `input.subagent_session_id`. This has always been the case — the original code never read the input field either. Adding an `input.subagent_session_id` fallback would be a new capability, not a bug fix. Do NOT flag the absence of this fallback as a regression in PR `#247` or future PRs unless there is a concrete case where metadata is not populated.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T10:33:12.228Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:12.228Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/prompt.ts` and `packages/opencode/src/session/processor.ts`, PR `#264`), the loop-gate race condition between `buildLoopContext()` and `recordSyntheticBlock`/`recordSyntheticStop` is intentionally handled via idempotence guards (re-check sigKey presence / `hasStopped` inside the record helpers) rather than a full per-parent `Effect.Mutex`. Threading a `Map<MessageID, Mutex>` through the processor was considered too large a surface change for this edge case; the residual TOCTOU window only produces extra synthetic parts with no behavioral drift on the "turn ends" contract. A code comment documents the trade-off and points to a full-mutex follow-up if the race is observed in practice. Do NOT re-flag the absence of a per-parent mutex as a blocking issue in future reviews.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-24T06:50:05.142Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 211
File: packages/opencode/src/provider/models.ts:113-179
Timestamp: 2026-04-24T06:50:05.142Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/provider/models.ts`), `PublishModel` and `PublishProvider` intentionally duplicate fields from `Model` and `Provider` with looser optionality. The split is a deliberate behavior boundary: `PublishModel`/`PublishProvider` (with `.passthrough()`) are used for lenient publish-time validation of incoming catalogs, while `Model`/`Provider` are used for strict runtime normalization. Do not flag this duplication as a maintenance burden or suggest extracting a shared base schema — the explicit separation is intentional and tied to the models-refresh reliability path introduced in PR `#211`.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T10:08:57.448Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/diagnostics.test.ts:313-318
Timestamp: 2026-04-27T10:08:57.448Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `SessionDiagnostics.LoopAction` is a TypeScript string literal union type (`"observe" | "block" | "stop"`), NOT a runtime enum. There is no runtime form to introspect via `Object.values()`. The test pattern `const all: SessionDiagnostics.LoopAction[] = ["observe", "block", "stop"]; expect(all).toHaveLength(3)` is intentional and correct — it provides compile-time exhaustiveness via the type annotation. Do NOT suggest replacing this with `Object.values()` introspection or flag the runtime length check as insufficient.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-25T12:52:39.403Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 234
File: packages/opencode/src/session/export.ts:75-88
Timestamp: 2026-04-25T12:52:39.403Z
Learning: In `packages/opencode/src/session/export.ts` (Astro-Han/pawwork), the `extractReasonFromCause` function intentionally uses a lightweight cast-based inspection of the Cause `reasons` array instead of `Cause.failures()` / `Cause.defects()` utilities. This is a deliberate choice: the function is diagnostic-only (used to populate a best-effort reason string for export diagnostics), and the maintainer explicitly prefers not to add coupling to Effect's Cause API surface for this purpose. Do not flag this pattern as fragile or suggest replacing it with Cause utilities.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T08:27:43.672Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:255-279
Timestamp: 2026-04-27T08:27:43.672Z
Learning: In `packages/opencode/src/session/diagnostics.ts` (PR `#264`, issue `#229`), target-level loop detection is intentionally tool-scoped. The `real` filter includes `r.tool === input.tool` and target signature keys are formatted as `target:${tool}:${targetHash}`. Cross-tool retries on the same URL/path/query are considered legitimate exploration (e.g., webfetch failed → switch to a different tool), NOT a stuck loop. Only same-tool + same-target accumulation counts toward block/stop. Do NOT suggest removing the tool scope from target signature keys or the `real` filter.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T11:18:47.332Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/mcp-exa.test.ts:1-186
Timestamp: 2026-04-27T11:18:47.332Z
Learning: In `packages/opencode/test/tool/mcp-exa.test.ts` (Astro-Han/pawwork), the tests intentionally use raw `bun:test` async cases with `Effect.runPromise(...)` and per-case `HttpClient.make(...)` fakes rather than the `testEffect(...)` harness. The maintainer has explicitly decided not to migrate, because the HttpClient fake wiring is itself the behavior under test and switching to `testEffect` would be style churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T11:19:24.963Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch-auth.test.ts:0-0
Timestamp: 2026-04-27T11:19:24.963Z
Learning: In `packages/opencode/test/tool/websearch-auth.test.ts` (Astro-Han/pawwork), the tests intentionally use a small local `runWith` runner with raw `bun:test` and `Effect.runPromise` rather than the `testEffect` harness. Each test case injects a custom in-memory `Auth.Service` layer; switching to `testEffect` would be style-only churn without changing risk coverage. Do not flag these tests as needing harness migration.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T10:33:02.677Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:33:02.677Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/session/diagnostics.ts`, PR `#264`), `loopLastError` is stored as raw (unscrubbed) error text in loop metadata intentionally. The trust boundary for sensitive data scrubbing is the renderer (`LoopRenderer.render` already applies `scrubErrorText` before any user/model-facing output), not the storage layer. Failed tool parts also already store raw errors in `state.error` (PR `#204` default behavior), so scrubbing only `loopLastError` would not close that pre-existing leak and would add lossy storage. If export-side hardening is needed, the correct approach is a single sanitizer pass over both `state.error` and metadata in the export layer. Do NOT suggest scrubbing `loopLastError` at the `observeToolError` call site.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T11:18:47.596Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/agent/agent.test.ts:440-447
Timestamp: 2026-04-27T11:18:47.596Z
Learning: In `packages/opencode/test/agent/agent.test.ts` (Astro-Han/pawwork), all agent-permission tests intentionally use the manual `tmpdir()` + `Instance.provide(...)` pattern. Do not flag individual tests in this file for conversion to `provideTmpdirInstance(...)` or `provideInstance(...)`; a full harness migration would be a separate PR if the pattern ever needs to change.

Applied to files:

  • packages/opencode/test/session/session.test.ts
  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-24T03:51:56.211Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:56.211Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.

Applied to files:

  • packages/opencode/test/session/session.test.ts
📚 Learning: 2026-04-21T16:57:25.580Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/agent.ts:108-119
Timestamp: 2026-04-21T16:57:25.580Z
Learning: In `packages/opencode/src/config/agent.ts` (Astro-Han/pawwork), `ConfigPermission.Info` only accepts permission objects or the three action strings `"ask"`, `"allow"`, `"deny"`, and transforms those action strings into `{ "*": action }` before `normalize()` runs. By the time `normalize()` is reached, `configuredPermission` is always either `undefined` or a `Record<string, Rule>` — never a raw arbitrary string. The `Object.assign(permission, configuredPermission)` pattern is therefore safe. Do not flag it as corrupting string permission references.

Applied to files:

  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/test/config/config.test.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T11:18:47.298Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 271
File: packages/opencode/test/tool/websearch.test.ts:21-78
Timestamp: 2026-04-27T11:18:47.298Z
Learning: In `packages/opencode/test/tool/websearch.test.ts`, the tests intentionally use manual `Effect.runPromise` with explicit `Effect.provide(...)` chains (including `Layer.succeed(Auth.Service, ...)`, `Layer.succeed(HttpClient.HttpClient, http)`, `WebSearchAuth.layer`, `Truncate.defaultLayer`, and `Agent.defaultLayer`) rather than the `testEffect(...)` harness. This is by design: the fake Auth and HTTP recovery-metadata layers must be explicitly injected and kept visible/scoped at the test site. Do NOT suggest migrating these tests to `testEffect` or removing the manual layer provides.

Applied to files:

  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-27T12:59:49.844Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/test/session/prompt-effect.test.ts:0-0
Timestamp: 2026-04-27T12:59:49.844Z
Learning: In `packages/opencode/test/session/prompt-effect.test.ts` and `packages/opencode/src/session/diagnostics.ts` (PR `#264`), the recovery reminder copy differs between signature kinds: the input-repeat variant says "repeated the same tool input 3 times" (uses a literal count), while the target-repeat variant says "failed against the same target multiple times" (uses "multiple times" with no count). Assertions that check for injected reminder text in LLM inputs must accept both phrasings when a scenario produces both `input:` and `target:` signatures (e.g., `read` tool with a `filePath` parameter). Do NOT narrow the assertion to only the input-variant phrasing.

Applied to files:

  • packages/opencode/test/permission-agent.test.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/test/permission/next.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/test/permission/next.test.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Defect` instead of `unknown` for defect-like causes in Effect code

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.Class` for multi-field data in Effect schemas

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Schema.TaggedErrorClass` for typed errors in Effect schemas

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition

Applied to files:

  • packages/opencode/test/util/effect-zod.test.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-27T08:58:00.665Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:531-538
Timestamp: 2026-04-27T08:58:00.665Z
Learning: When using Effect (e.g., `yield*` with `Effect`-style generator yielding), only use `yield* new SomeErrorClass(...)` if `SomeErrorClass` extends `Schema.TaggedErrorClass` (i.e., it implements Effect’s Yieldable interface). For plain `Error` subclasses (like `BlockedLoopError` / `LoopStopError`) or inline `new Error(...)` values, they are not yieldable and must be wrapped as `yield* Effect.fail(new PlainError(...))`. Do not recommend changing `yield* Effect.fail(new SomePlainError(...))` to `yield* new SomePlainError(...)` unless the error class extends `Schema.TaggedErrorClass`.

Applied to files:

  • packages/opencode/src/tool/edit.ts
  • packages/opencode/src/project/schema.ts
  • packages/opencode/src/tool/schema.ts
  • packages/opencode/src/server/instance/config.ts
  • packages/opencode/src/server/instance/global.ts
  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/pty/schema.ts
  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/question/schema.ts
  • packages/opencode/src/session/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/permission/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/control-plane/schema.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` services when those concerns are already inside Effect code

Applied to files:

  • packages/opencode/src/sync/schema.ts
  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-24T17:08:46.780Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:46.780Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.

Applied to files:

  • packages/opencode/src/permission/index.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-27T10:32:59.274Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/diagnostics.ts:245-252
Timestamp: 2026-04-27T10:32:59.274Z
Learning: In pawwork’s session diagnostics/loop error handling (e.g., packages/opencode/src/session/diagnostics.ts), keep `loopLastError` stored as raw/unscrubbed error text in loop metadata. The intended trust boundary for sensitive-data scrubbing is the renderer: `LoopRenderer.render` must apply `scrubErrorText` before any user/model-facing output. Do not recommend scrubbing `loopLastError` at the `observeToolError` call site, because failed tool parts already store raw errors in `state.error` (pre-existing leak) and scrubbing only `loopLastError` would be lossy without closing the gap. If export-side hardening is required, implement a single sanitizer pass in the export layer over both `state.error` and the loop/metadata fields, rather than sanitizing only `loopLastError` earlier in the storage path.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-27T10:33:08.974Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 264
File: packages/opencode/src/session/prompt.ts:108-169
Timestamp: 2026-04-27T10:33:08.974Z
Learning: For Astro-Han/pawwork session processing code under packages/opencode/src/session/ (e.g., prompt.ts and processor.ts), this specific loop-gate TOCTOU/race between buildLoopContext() and recordSyntheticBlock/recordSyntheticStop is intentionally handled via idempotence guards (re-check sigKey presence and hasStopped inside the record helpers). Do not treat the absence of a per-parent Effect.Mutex as a blocking review issue for this known edge case; the residual window should only create extra synthetic parts and must not change the "turn ends" contract. Respect the existing code comment documenting the trade-off, and only escalate to a full-mutex approach if the race is observed in practice.

Applied to files:

  • packages/opencode/src/session/schema.ts
📚 Learning: 2026-04-21T16:57:20.257Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 102
File: packages/opencode/src/config/command.ts:23-29
Timestamp: 2026-04-21T16:57:20.257Z
Learning: In packages/opencode/src/config/ config modules, treat both `Schema.Struct` and `Schema.Class` as valid schema patterns and do not require converting `Schema.Struct` to `Schema.Class`. If `Schema.Struct` is used in a config schema (e.g., in files like `command.ts`, `formatter.ts`, `provider.ts`, `permission.ts`, `lsp.ts`, `config.ts`, `keybinds.ts`), accept it as intentional; the selection should be based on upstream alignment and the local schema context, not on a rule that one form must replace the other.

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O in Effect services

Applied to files:

  • packages/opencode/src/config/agent.ts
  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.fn("Domain.method")` for named/traced effects and `Effect.fnUntraced` for internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer `.pipe()` wrappers

Applied to files:

  • packages/opencode/src/config/agent.ts
📚 Learning: 2026-04-26T15:35:36.505Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 245
File: packages/opencode/src/question/index.ts:21-24
Timestamp: 2026-04-26T15:35:36.505Z
Learning: In Astro-Han/pawwork (`packages/opencode/src/question/index.ts`), `Question.Option.description` is intentionally **required** (`z.string()`, not `.optional()`). This matches the upstream opencode contract and all current callers (e.g. `PlanExitTool`) always supply a description. The defensive `<Show when={props.description}>` rendering in `session-question-dock.tsx` is a standard guard, not a signal that the field is intended to be optional. Do NOT suggest making `Option.description` optional without a dedicated follow-up that covers schema + tool description + dock copy + tests.

Applied to files:

  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : In `Effect.gen` / `Effect.fn`, prefer `yield* new MyError(...)` over `yield* Effect.fail(new MyError(...))` for direct early-failure branches

Applied to files:

  • packages/opencode/src/tool/todo.ts
  • packages/opencode/src/util/effect-zod.ts
📚 Learning: 2026-04-20T14:21:56.373Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 71
File: packages/app/src/components/session/session-status-connections.tsx:146-147
Timestamp: 2026-04-20T14:21:56.373Z
Learning: In the Astro-Han/pawwork repository (SolidJS app), `sync.data.config` is always initialized to `{}` at `packages/app/src/context/global-sync.tsx` line 71 and is never `undefined` at runtime. Non-optional property access like `sync.data.config.plugin` is intentional and consistent with the pattern used in `packages/app/src/components/status-popover-body.tsx` line 243. Do not flag `sync.data.config.plugin` as needing optional chaining.

Applied to files:

  • packages/opencode/src/util/effect-zod.ts
  • packages/opencode/src/config/permission.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually

Applied to files:

  • packages/opencode/src/config/config.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `InstanceState` from `src/effect/instance-state.ts` for per-directory or per-project state that needs per-instance cleanup; do work directly in the `InstanceState.make` closure where `ScopedCache` handles run-once semantics

Applied to files:

  • packages/opencode/src/config/config.ts

@Astro-Han Astro-Han merged commit b7f2482 into dev Apr 28, 2026
25 checks passed
@Astro-Han Astro-Han deleted the claude/upstream-sync-209-pr3-effect-schema branch April 28, 2026 03:57
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.

1 participant