Просмотр исходного кода

refactor(core): replace legacy logger with Effect logging (#31310)

Dax 4 месяцев назад
Родитель
Сommit
c06ad7c881
100 измененных файлов с 634 добавлено и 1945 удалено
  1. 0 73
      packages/core/src/effect/logger.ts
  2. 0 107
      packages/core/src/effect/observability.ts
  3. 1 1
      packages/core/src/effect/runtime.ts
  4. 1 3
      packages/core/src/event.ts
  5. 5 6
      packages/core/src/filesystem/ripgrep.ts
  6. 32 29
      packages/core/src/filesystem/search.ts
  7. 10 10
      packages/core/src/filesystem/watcher.ts
  8. 1 1
      packages/core/src/models-dev.ts
  9. 21 0
      packages/core/src/observability.ts
  10. 66 0
      packages/core/src/observability/logging.ts
  11. 79 0
      packages/core/src/observability/otlp.ts
  12. 1 0
      packages/core/src/observability/shared.ts
  13. 1 3
      packages/core/src/project-reference.ts
  14. 4 7
      packages/core/src/pty.ts
  15. 1 1
      packages/core/src/session/logging.ts
  16. 4 11
      packages/core/src/skill/discovery.ts
  17. 0 197
      packages/core/src/util/log.ts
  18. 0 24
      packages/core/src/util/opencode-process.ts
  19. 64 1
      packages/core/test/effect/observability.test.ts
  20. 0 3
      packages/desktop/src/main/env.d.ts
  21. 1 2
      packages/desktop/src/main/sidecar.ts
  22. 3 12
      packages/opencode/src/acp/event.ts
  23. 3 24
      packages/opencode/src/acp/permission.ts
  24. 5 19
      packages/opencode/src/acp/service.ts
  25. 3 14
      packages/opencode/src/acp/usage.ts
  26. 1 4
      packages/opencode/src/cli/cmd/acp.ts
  27. 2 3
      packages/opencode/src/cli/cmd/debug/lsp.ts
  28. 0 3
      packages/opencode/src/cli/cmd/debug/scrap.ts
  29. 6 30
      packages/opencode/src/cli/cmd/run/footer.ts
  30. 0 117
      packages/opencode/src/cli/cmd/run/otel.ts
  31. 0 24
      packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts
  32. 1 89
      packages/opencode/src/cli/cmd/run/runtime.ts
  33. 1 12
      packages/opencode/src/cli/cmd/run/scrollback.surface.ts
  34. 4 44
      packages/opencode/src/cli/cmd/tui.ts
  35. 1 15
      packages/opencode/src/cli/heap.ts
  36. 0 28
      packages/opencode/src/cli/tui/worker.ts
  37. 2 11
      packages/opencode/src/config/agent.ts
  38. 1 7
      packages/opencode/src/config/command.ts
  39. 18 25
      packages/opencode/src/config/config.ts
  40. 2 10
      packages/opencode/src/config/managed.ts
  41. 6 28
      packages/opencode/src/config/tui-migrate.ts
  42. 13 31
      packages/opencode/src/config/tui.ts
  43. 24 116
      packages/opencode/src/control-plane/workspace.ts
  44. 1 1
      packages/opencode/src/effect/app-runtime.ts
  45. 1 1
      packages/opencode/src/effect/bootstrap-runtime.ts
  46. 1 4
      packages/opencode/src/effect/instance-state.ts
  47. 1 1
      packages/opencode/src/effect/run-service.ts
  48. 8 18
      packages/opencode/src/format/index.ts
  49. 0 9
      packages/opencode/src/ide/index.ts
  50. 4 10
      packages/opencode/src/image/image.ts
  51. 3 68
      packages/opencode/src/index.ts
  52. 1 4
      packages/opencode/src/installation/index.ts
  53. 1 37
      packages/opencode/src/lsp/client.ts
  54. 7 17
      packages/opencode/src/lsp/lsp.ts
  55. 0 124
      packages/opencode/src/lsp/server.ts
  56. 1 34
      packages/opencode/src/mcp/index.ts
  57. 0 9
      packages/opencode/src/mcp/oauth-callback.ts
  58. 0 10
      packages/opencode/src/mcp/oauth-provider.ts
  59. 0 1
      packages/opencode/src/node.ts
  60. 4 7
      packages/opencode/src/patch/index.ts
  61. 2 5
      packages/opencode/src/permission/index.ts
  62. 2 9
      packages/opencode/src/plugin/digitalocean.ts
  63. 0 3
      packages/opencode/src/plugin/github-copilot/copilot.ts
  64. 5 17
      packages/opencode/src/plugin/index.ts
  65. 0 5
      packages/opencode/src/plugin/openai/codex.ts
  66. 0 19
      packages/opencode/src/plugin/openai/ws-pool.ts
  67. 8 52
      packages/opencode/src/plugin/tui/runtime.ts
  68. 2 9
      packages/opencode/src/plugin/xai.ts
  69. 1 1
      packages/opencode/src/project/bootstrap.ts
  70. 4 6
      packages/opencode/src/project/instance-store.ts
  71. 2 5
      packages/opencode/src/project/project.ts
  72. 0 7
      packages/opencode/src/project/vcs.ts
  73. 0 30
      packages/opencode/src/provider/provider.ts
  74. 5 8
      packages/opencode/src/question/index.ts
  75. 1 3
      packages/opencode/src/reference/reference.ts
  76. 1 10
      packages/opencode/src/server/global-lifecycle.ts
  77. 3 16
      packages/opencode/src/server/mdns.ts
  78. 9 3
      packages/opencode/src/server/routes/instance/httpapi/handlers/control.ts
  79. 2 5
      packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts
  80. 2 5
      packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts
  81. 30 31
      packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts
  82. 1 3
      packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts
  83. 3 9
      packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts
  84. 1 4
      packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts
  85. 9 12
      packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts
  86. 14 15
      packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts
  87. 1 1
      packages/opencode/src/server/routes/instance/httpapi/server.ts
  88. 3 4
      packages/opencode/src/server/server.ts
  89. 2 10
      packages/opencode/src/server/shared/fence.ts
  90. 6 7
      packages/opencode/src/session/compaction.ts
  91. 30 43
      packages/opencode/src/session/llm.ts
  92. 1 2
      packages/opencode/src/session/message-v2.ts
  93. 10 6
      packages/opencode/src/session/processor.ts
  94. 26 24
      packages/opencode/src/session/prompt.ts
  95. 1 4
      packages/opencode/src/session/revert.ts
  96. 3 5
      packages/opencode/src/session/session.ts
  97. 0 4
      packages/opencode/src/session/tools.ts
  98. 7 13
      packages/opencode/src/share/share-next.ts
  99. 11 18
      packages/opencode/src/skill/discovery.ts
  100. 5 12
      packages/opencode/src/skill/index.ts

+ 0 - 73
packages/core/src/effect/logger.ts

@@ -1,73 +0,0 @@
-import { Cause, Effect, Logger, References } from "effect"
-import * as Log from "../util/log"
-
-type Fields = Record<string, unknown>
-
-const normalizeKey = (key: string) => (key === "sessionID" ? "session.id" : key)
-
-export interface Handle {
-  readonly debug: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
-  readonly info: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
-  readonly warn: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
-  readonly error: (msg?: unknown, extra?: Fields) => Effect.Effect<void>
-  readonly with: (extra: Fields) => Handle
-}
-
-const clean = (input?: Fields): Fields =>
-  Object.fromEntries(
-    Object.entries(input ?? {})
-      .filter((entry) => entry[1] !== undefined && entry[1] !== null)
-      .map(([key, value]) => [normalizeKey(key), value]),
-  )
-
-const text = (input: unknown): string => {
-  // oxlint-disable-next-line no-base-to-string
-  if (Array.isArray(input)) return input.map((item) => String(item)).join(" ")
-  // oxlint-disable-next-line no-base-to-string
-  return input === undefined ? "" : String(input)
-}
-
-const call = (run: (msg?: unknown) => Effect.Effect<void>, base: Fields, msg?: unknown, extra?: Fields) => {
-  const ann = clean({ ...base, ...extra })
-  const fx = run(msg)
-  return Object.keys(ann).length ? Effect.annotateLogs(fx, ann) : fx
-}
-
-export const logger = Logger.make((opts) => {
-  const extra = clean(opts.fiber.getRef(References.CurrentLogAnnotations))
-  const now = opts.date.getTime()
-  for (const [key, start] of opts.fiber.getRef(References.CurrentLogSpans)) {
-    extra[`logSpan.${key}`] = `${now - start}ms`
-  }
-  if (opts.cause.reasons.length > 0) {
-    extra.cause = Cause.pretty(opts.cause)
-  }
-
-  const svc = typeof extra.service === "string" ? extra.service : undefined
-  if (svc) delete extra.service
-  const log = svc ? Log.create({ service: svc }) : Log.Default
-  const msg = text(opts.message)
-
-  switch (opts.logLevel) {
-    case "Trace":
-    case "Debug":
-      return log.debug(msg, extra)
-    case "Warn":
-      return log.warn(msg, extra)
-    case "Error":
-    case "Fatal":
-      return log.error(msg, extra)
-    default:
-      return log.info(msg, extra)
-  }
-})
-
-export const layer = Logger.layer([logger], { mergeWithExisting: false })
-
-export const create = (base: Fields = {}): Handle => ({
-  debug: (msg, extra) => call((item) => Effect.logDebug(item), base, msg, extra),
-  info: (msg, extra) => call((item) => Effect.logInfo(item), base, msg, extra),
-  warn: (msg, extra) => call((item) => Effect.logWarning(item), base, msg, extra),
-  error: (msg, extra) => call((item) => Effect.logError(item), base, msg, extra),
-  with: (extra) => create({ ...base, ...extra }),
-})

+ 0 - 107
packages/core/src/effect/observability.ts

@@ -1,107 +0,0 @@
-import { Effect, Layer, Logger } from "effect"
-import { FetchHttpClient } from "effect/unstable/http"
-import { OtlpLogger, OtlpSerialization } from "effect/unstable/observability"
-import * as EffectLogger from "./logger"
-import { Flag } from "../flag/flag"
-import { InstallationChannel, InstallationVersion } from "../installation/version"
-import { ensureProcessMetadata } from "../util/opencode-process"
-
-const base = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
-export const enabled = !!base
-const processID = crypto.randomUUID()
-
-const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
-  ? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce(
-      (acc, x) => {
-        const [key, ...value] = x.split("=")
-        acc[key] = value.join("=")
-        return acc
-      },
-      {} as Record<string, string>,
-    )
-  : undefined
-
-export function resource(): { serviceName: string; serviceVersion: string; attributes: Record<string, string> } {
-  const processMetadata = ensureProcessMetadata("main")
-  const attributes: Record<string, string> = (() => {
-    const value = process.env.OTEL_RESOURCE_ATTRIBUTES
-    if (!value) return {}
-    try {
-      return Object.fromEntries(
-        value.split(",").map((entry) => {
-          const index = entry.indexOf("=")
-          if (index < 1) throw new Error("Invalid OTEL_RESOURCE_ATTRIBUTES entry")
-          return [decodeURIComponent(entry.slice(0, index)), decodeURIComponent(entry.slice(index + 1))]
-        }),
-      )
-    } catch {
-      return {}
-    }
-  })()
-
-  return {
-    serviceName: "opencode",
-    serviceVersion: InstallationVersion,
-    attributes: {
-      ...attributes,
-      "deployment.environment.name": InstallationChannel,
-      "opencode.client": Flag.OPENCODE_CLIENT,
-      "opencode.process_role": processMetadata.processRole,
-      "opencode.run_id": processMetadata.runID,
-      "service.instance.id": processID,
-    },
-  }
-}
-
-function logs() {
-  return Logger.layer(
-    [
-      EffectLogger.logger,
-      OtlpLogger.make({
-        url: `${base}/v1/logs`,
-        resource: resource(),
-        headers,
-      }),
-    ],
-    { mergeWithExisting: false },
-  ).pipe(Layer.provide(OtlpSerialization.layerJson), Layer.provide(FetchHttpClient.layer))
-}
-
-const traces = async () => {
-  const NodeSdk = await import("@effect/opentelemetry/NodeSdk")
-  const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
-  const SdkBase = await import("@opentelemetry/sdk-trace-base")
-
-  // @effect/opentelemetry creates a NodeTracerProvider but never calls
-  // register(), so the global @opentelemetry/api context manager stays
-  // as the no-op default. Non-Effect code (like the AI SDK) that calls
-  // tracer.startActiveSpan() relies on context.active() to find the
-  // parent span - without a real context manager every span starts a
-  // new trace. Registering AsyncLocalStorageContextManager fixes this.
-  const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks")
-  const { context } = await import("@opentelemetry/api")
-  const mgr = new AsyncLocalStorageContextManager()
-  mgr.enable()
-  context.setGlobalContextManager(mgr)
-
-  return NodeSdk.layer(() => ({
-    resource: resource(),
-    spanProcessor: new SdkBase.BatchSpanProcessor(
-      new OTLP.OTLPTraceExporter({
-        url: `${base}/v1/traces`,
-        headers,
-      }),
-    ),
-  }))
-}
-
-export const layer = !base
-  ? EffectLogger.layer
-  : Layer.unwrap(
-      Effect.gen(function* () {
-        const trace = yield* Effect.promise(traces)
-        return Layer.mergeAll(trace, logs())
-      }),
-    )
-
-export const Observability = { enabled, layer }

+ 1 - 1
packages/core/src/effect/runtime.ts

@@ -1,6 +1,6 @@
 import { Layer, type Context, ManagedRuntime, type Effect } from "effect"
 import { memoMap } from "./memo-map"
-import { Observability } from "./observability"
+import { Observability } from "../observability"
 
 export function makeRuntime<I, S, E>(service: Context.Service<I, S>, layer: Layer.Layer<I, E>) {
   let rt: ManagedRuntime.ManagedRuntime<I, E> | undefined

+ 1 - 3
packages/core/src/event.ts

@@ -410,9 +410,7 @@ export const layerWith = (options?: LayerOptions) =>
           Effect.catchCauseIf(
             (cause) => !Cause.hasInterrupts(cause),
             (cause) =>
-              Effect.logError("Event observer failed").pipe(
-                Effect.annotateLogs({ eventID: event.id, eventType: event.type, kind, cause }),
-              ),
+              Effect.logError("Event observer failed", { eventID: event.id, eventType: event.type, kind, cause }),
           ),
         )
 

+ 5 - 6
packages/core/src/filesystem/ripgrep.ts

@@ -10,11 +10,8 @@ import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner
 import { CrossSpawnSpawner } from "../cross-spawn-spawner"
 import { Global } from "../global"
 import { NonNegativeInt } from "../schema"
-import * as Log from "../util/log"
-import { sanitizedProcessEnv } from "../util/opencode-process"
 import { which } from "../util/which"
 
-const log = Log.create({ service: "ripgrep" })
 const VERSION = "15.1.0"
 const PLATFORM = {
   "arm64-darwin": { platform: "aarch64-apple-darwin", extension: "tar.gz" },
@@ -146,7 +143,9 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Ri
 export const use = serviceUse(Service)
 
 function env() {
-  const env = sanitizedProcessEnv()
+  const env = Object.fromEntries(
+    Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
+  )
   delete env.RIPGREP_CONFIG_PATH
   return env
 }
@@ -307,7 +306,7 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | ChildProcessSpa
           const url = `https://github.com/BurntSushi/ripgrep/releases/download/${VERSION}/${filename}`
           const archive = path.join(Global.Path.bin, filename)
 
-          log.info("downloading ripgrep", { url })
+          yield* Effect.logInfo("downloading ripgrep", { url })
           yield* fs.ensureDir(Global.Path.bin).pipe(Effect.orDie)
 
           const bytes = yield* HttpClientRequest.get(url).pipe(
@@ -418,7 +417,7 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | ChildProcessSpa
       })
 
       const tree: Interface["tree"] = Effect.fn("Ripgrep.tree")(function* (input: TreeInput) {
-        log.info("tree", input)
+        yield* Effect.logInfo("tree", input)
         const list = Array.from(yield* files({ cwd: input.cwd, signal: input.signal }).pipe(Stream.runCollect))
 
         interface Node {

+ 32 - 29
packages/core/src/filesystem/search.ts

@@ -4,13 +4,11 @@ import type { PlatformError } from "effect/PlatformError"
 import { FSUtil } from "../fs-util"
 import { Glob } from "../util/glob"
 import { Global } from "../global"
-import * as Log from "../util/log"
 import { serviceUse } from "../effect/service-use"
 import { makeRuntime } from "../effect/runtime"
 import { Fff } from "#fff"
 import { Ripgrep } from "./ripgrep"
 
-const log = Log.create({ service: "file.search" })
 const root = path.join(Global.Path.cache, "fff")
 
 export type Item = Ripgrep.Item
@@ -220,12 +218,14 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
         if (!scanned.ok || !scanned.value) {
           yield* fffSync("destroy picker", () => pick.destroy()).pipe(Effect.ignore)
           state.pick.delete(dir)
-          log.warn("fff scan not ready", { dir })
+          yield* Effect.logWarning("fff scan not ready", { dir })
           return yield* Effect.fail(new Error(scanned.ok ? "fff scan timed out" : scanned.error))
         }
 
         const git = yield* fffSync("refresh git status", () => pick.refreshGitStatus())
-        if (!git.ok) log.warn("fff git refresh failed", { dir, error: git.error })
+        if (!git.ok) {
+          yield* Effect.logWarning("fff git refresh failed", { dir, error: git.error })
+        }
       })
 
     // Create (or return) the picker for a directory. Creation is synchronous
@@ -244,10 +244,7 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
       if (pending) return yield* Deferred.await(pending)
 
       const available = yield* fffSync("check availability", () => Fff.available()).pipe(
-        Effect.catch((error) => {
-          log.warn("fff availability check failed", { error })
-          return Effect.succeed(false)
-        }),
+        Effect.catch((error) => Effect.logWarning("fff availability check failed", { error }).pipe(Effect.as(false))),
       )
       if (!available) return undefined
 
@@ -272,7 +269,7 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
           }),
         )
         if (!made.ok) {
-          log.warn("fff init failed", { dir, error: made.error })
+          yield* Effect.logWarning("fff init failed", { dir, error: made.error })
           const err = new Error(made.error)
           yield* Deferred.fail(gate, err)
           return yield* Effect.fail(err)
@@ -355,14 +352,15 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
           pageSize: limit,
         }),
       ).pipe(
-        Effect.catch((error) => {
-          log.warn(`fff ${kind} search failed`, { dir, query, error })
-          return Effect.succeed<Fff.Result<string[]> | undefined>(undefined)
-        }),
+        Effect.catch((error) =>
+          Effect.logWarning(`fff ${kind} search failed`, { dir, query, error }).pipe(
+            Effect.as<Fff.Result<string[]> | undefined>(undefined),
+          ),
+        ),
       )
       if (!fffResult) return undefined
       if (!fffResult.ok) {
-        log.warn(`fff ${kind} search failed`, { dir, query, error: fffResult.error })
+        yield* Effect.logWarning(`fff ${kind} search failed`, { dir, query, error: fffResult.error })
         return undefined
       }
 
@@ -393,14 +391,15 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
           timeBudgetMs: 1_500,
         }),
       ).pipe(
-        Effect.catch((error) => {
-          log.warn("fff grep failed", { dir, pattern: input.pattern, error })
-          return Effect.succeed<Fff.Result<Fff.Grep> | undefined>(undefined)
-        }),
+        Effect.catch((error) =>
+          Effect.logWarning("fff grep failed", { dir, pattern: input.pattern, error }).pipe(
+            Effect.as<Fff.Result<Fff.Grep> | undefined>(undefined),
+          ),
+        ),
       )
       if (!fffGrep) return yield* rip(input)
       if (!fffGrep.ok) {
-        log.warn("fff grep failed", { dir, pattern: input.pattern, error: fffGrep.error })
+        yield* Effect.logWarning("fff grep failed", { dir, pattern: input.pattern, error: fffGrep.error })
         return yield* rip(input)
       }
 
@@ -432,10 +431,11 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
             pageSize: limit,
           }),
         ).pipe(
-          Effect.catch((error) => {
-            log.warn("fff glob failed", { dir, pattern: input.pattern, error })
-            return Effect.succeed<Fff.Result<Fff.Search> | undefined>(undefined)
-          }),
+          Effect.catch((error) =>
+            Effect.logWarning("fff glob failed", { dir, pattern: input.pattern, error }).pipe(
+              Effect.as<Fff.Result<Fff.Search> | undefined>(undefined),
+            ),
+          ),
         )
 
         if (fffGlob?.ok) {
@@ -453,7 +453,7 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
             truncated: fffGlob.value.totalMatched > rows.length,
           }
         } else if (fffGlob) {
-          log.warn("fff glob failed", { dir, pattern: input.pattern, error: fffGlob.error })
+          yield* Effect.logWarning("fff glob failed", { dir, pattern: input.pattern, error: fffGlob.error })
           // fall through to the fallback
         }
       }
@@ -500,13 +500,16 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Ripgrep.Service
       if (!entry) return
 
       const out = yield* fffSync("track query", () => entry.pick.trackQuery(row.text, file)).pipe(
-        Effect.catch((error) => {
-          log.warn("fff track query failed", { dir: row.dir, query: row.text, file, error })
-          return Effect.succeed<Fff.Result<boolean> | undefined>(undefined)
-        }),
+        Effect.catch((error) =>
+          Effect.logWarning("fff track query failed", { dir: row.dir, query: row.text, file, error }).pipe(
+            Effect.as<Fff.Result<boolean> | undefined>(undefined),
+          ),
+        ),
       )
       if (!out) return
-      if (!out.ok) log.warn("fff track query failed", { dir: row.dir, query: row.text, file, error: out.error })
+      if (!out.ok) {
+        yield* Effect.logWarning("fff track query failed", { dir: row.dir, query: row.text, file, error: out.error })
+      }
     })
 
     return Service.of({ files, tree, search, file, glob, open, warm, release })

+ 10 - 10
packages/core/src/filesystem/watcher.ts

@@ -12,13 +12,11 @@ import { FSUtil } from "../fs-util"
 import { Git } from "../git"
 import { Location } from "../location"
 import { lazy } from "../util/lazy"
-import * as Log from "../util/log"
 import { Ignore } from "./ignore"
 import { Protected } from "./protected"
 
 declare const OPENCODE_LIBC: string | undefined
 
-const log = Log.create({ service: "file.watcher" })
 const SUBSCRIBE_TIMEOUT_MS = 10_000
 
 export const Event = {
@@ -38,8 +36,7 @@ const watcher = lazy((): typeof import("@parcel/watcher") | undefined => {
       `@parcel/watcher-${process.platform}-${process.arch}${process.platform === "linux" ? `-${libc || "glibc"}` : ""}`,
     )
     return createWrapper(binding) as typeof import("@parcel/watcher")
-  } catch (error) {
-    log.error("failed to load watcher binding", { error })
+  } catch {
     return
   }
 })
@@ -71,14 +68,17 @@ export const layer = Layer.effect(
     const backend = getBackend()
     const location = yield* Location.Service
     if (!backend) {
-      log.error("watcher backend not supported", { directory: location.directory, platform: process.platform })
+      yield* Effect.logError("watcher backend not supported", {
+        directory: location.directory,
+        platform: process.platform,
+      })
       return Service.of({})
     }
 
     const w = watcher()
     if (!w) return Service.of({})
 
-    log.info("watcher backend", { directory: location.directory, platform: process.platform, backend })
+    yield* Effect.logInfo("watcher backend", { directory: location.directory, platform: process.platform, backend })
     const events = yield* EventV2.Service
     const fs = yield* FSUtil.Service
     const git = yield* Git.Service
@@ -103,9 +103,8 @@ export const layer = Layer.effect(
         Effect.tap((subscription) => Effect.sync(() => subscriptions.push(subscription))),
         Effect.timeout(SUBSCRIBE_TIMEOUT_MS),
         Effect.catchCause((cause) => {
-          log.error("failed to subscribe", { directory, cause: Cause.pretty(cause) })
           pending.then((subscription) => subscription.unsubscribe()).catch(() => {})
-          return Effect.void
+          return Effect.logError("failed to subscribe", { directory, cause: Cause.pretty(cause) })
         }),
       )
     }
@@ -133,8 +132,9 @@ export const layer = Layer.effect(
     return Service.of({})
   }).pipe(
     Effect.catchCause((cause) => {
-      log.error("failed to init watcher service", { cause: Cause.pretty(cause) })
-      return Effect.succeed(Service.of({}))
+      return Effect.logError("failed to init watcher service", { cause: Cause.pretty(cause) }).pipe(
+        Effect.as(Service.of({})),
+      )
     }),
   ),
 )

+ 1 - 1
packages/core/src/models-dev.ts

@@ -228,7 +228,7 @@ export const layer = Layer.effect(
         }),
       ).pipe(
         Effect.tapCause((cause) =>
-          Effect.logError("Failed to fetch models.dev").pipe(Effect.annotateLogs("cause", cause)),
+          Effect.logError("Failed to fetch models.dev", { cause: cause }),
         ),
         Effect.ignore,
       )

+ 21 - 0
packages/core/src/observability.ts

@@ -0,0 +1,21 @@
+export * as Observability from "./observability"
+
+import { NodeFileSystem } from "@effect/platform-node"
+import { Effect, Layer, Logger, References } from "effect"
+import { FetchHttpClient } from "effect/unstable/http"
+import { OtlpSerialization } from "effect/unstable/observability"
+import { Logging } from "./observability/logging"
+import { Otlp } from "./observability/otlp"
+
+export const layer = Layer.unwrap(
+  Effect.gen(function* () {
+    const logs = Logger.layer([...Logging.loggers(), ...Otlp.loggers()], { mergeWithExisting: false }).pipe(
+      Layer.provide(NodeFileSystem.layer),
+      Layer.provide(OtlpSerialization.layerJson),
+      Layer.provide(FetchHttpClient.layer),
+      Layer.orDie,
+      Layer.merge(Layer.succeed(References.MinimumLogLevel, Logging.minimumLogLevel())),
+    )
+    return Layer.merge(logs, yield* Effect.promise(Otlp.tracingLayer))
+  }),
+)

+ 66 - 0
packages/core/src/observability/logging.ts

@@ -0,0 +1,66 @@
+import { Formatter, Logger, type LogLevel } from "effect"
+import path from "path"
+import { Global } from "../global"
+import { runID } from "./shared"
+
+function formatter(id: string = runID) {
+  return Logger.map(Logger.formatStructured, (output) => {
+    const messages = Array.isArray(output.message) ? output.message : [output.message]
+    return [
+      ["timestamp", output.timestamp],
+      ["level", output.level],
+      ["run", id],
+      ...messages.flatMap((value) => (plain(value) ? flatten(value) : [["message", value] as const])),
+      ...(output.cause === undefined ? [] : [["cause", output.cause] as const]),
+      ...flatten(output.spans),
+      ...flatten(output.annotations),
+    ]
+      .map(([key, value]) => `${key}=${format(value)}`)
+      .join(" ")
+  })
+}
+
+function flatten(input: Record<string, unknown>, prefix = "", seen = new WeakSet<object>()): Array<readonly [string, unknown]> {
+  if (seen.has(input)) return [[prefix, "[Circular]"]]
+  seen.add(input)
+  const entries = Object.entries(input)
+  if (entries.length === 0 && prefix) return [[prefix, input]]
+  return entries.flatMap(([key, value]) => {
+    const path = prefix ? `${prefix}.${key}` : key
+    return plain(value) ? flatten(value, path, seen) : [[path, value] as const]
+  })
+}
+
+function plain(input: unknown): input is Record<string, unknown> {
+  if (input === null || typeof input !== "object" || Array.isArray(input)) return false
+  const prototype = Object.getPrototypeOf(input)
+  return prototype === Object.prototype || prototype === null
+}
+
+function format(input: unknown) {
+  const value = typeof input === "string" ? input : Formatter.format(input)
+  return /^[^\s="\\]+$/.test(value) ? value : JSON.stringify(value)
+}
+
+export function fileLogger(file = path.join(Global.Path.log, "opencode.log"), id: string = runID) {
+  return Logger.toFile(formatter(id), file, { flag: "a", batchWindow: 0 })
+}
+
+const stderrLogger = Logger.make((options) => process.stderr.write(formatter().log(options) + "\n"))
+
+export function minimumLogLevel() {
+  const value = process.env.OPENCODE_LOG_LEVEL?.toUpperCase()
+  const levels = {
+    DEBUG: "Debug",
+    INFO: "Info",
+    WARN: "Warn",
+    ERROR: "Error",
+  } as const satisfies Record<string, LogLevel.LogLevel>
+  return value && value in levels ? levels[value as keyof typeof levels] : levels.INFO
+}
+
+export function loggers() {
+  return process.env.OPENCODE_PRINT_LOGS === "1" ? [fileLogger(), stderrLogger] : [fileLogger()]
+}
+
+export * as Logging from "./logging"

+ 79 - 0
packages/core/src/observability/otlp.ts

@@ -0,0 +1,79 @@
+import { Layer } from "effect"
+import { OtlpLogger } from "effect/unstable/observability"
+import { Flag } from "../flag/flag"
+import { InstallationChannel, InstallationVersion } from "../installation/version"
+import { runID } from "./shared"
+
+const endpoint = Flag.OTEL_EXPORTER_OTLP_ENDPOINT
+
+const headers = Flag.OTEL_EXPORTER_OTLP_HEADERS
+  ? Flag.OTEL_EXPORTER_OTLP_HEADERS.split(",").reduce(
+      (acc, entry) => {
+        const [key, ...value] = entry.split("=")
+        acc[key] = value.join("=")
+        return acc
+      },
+      {} as Record<string, string>,
+    )
+  : undefined
+
+function resourceAttributes() {
+  const value = process.env.OTEL_RESOURCE_ATTRIBUTES
+  if (!value) return {}
+  try {
+    return Object.fromEntries(
+      value.split(",").map((entry) => {
+        const index = entry.indexOf("=")
+        if (index < 1) throw new Error("Invalid OTEL_RESOURCE_ATTRIBUTES entry")
+        return [decodeURIComponent(entry.slice(0, index)), decodeURIComponent(entry.slice(index + 1))]
+      }),
+    )
+  } catch {
+    return {}
+  }
+}
+
+export function resource(): { serviceName: string; serviceVersion: string; attributes: Record<string, string> } {
+  return {
+    serviceName: "opencode",
+    serviceVersion: InstallationVersion,
+    attributes: {
+      ...resourceAttributes(),
+      "deployment.environment.name": InstallationChannel,
+      "opencode.client": Flag.OPENCODE_CLIENT,
+      "opencode.run": runID,
+      "service.instance.id": runID,
+    },
+  }
+}
+
+export function loggers() {
+  if (!endpoint) return []
+  return [OtlpLogger.make({ url: `${endpoint}/v1/logs`, resource: resource(), headers })]
+}
+
+export async function tracingLayer() {
+  if (!endpoint) return Layer.empty
+  const NodeSdk = await import("@effect/opentelemetry/NodeSdk")
+  const OTLP = await import("@opentelemetry/exporter-trace-otlp-http")
+  const SdkBase = await import("@opentelemetry/sdk-trace-base")
+  const { AsyncLocalStorageContextManager } = await import("@opentelemetry/context-async-hooks")
+  const { context } = await import("@opentelemetry/api")
+
+  // The Effect Node SDK does not register a global context manager, but the AI SDK uses it to parent spans.
+  const manager = new AsyncLocalStorageContextManager()
+  manager.enable()
+  context.setGlobalContextManager(manager)
+
+  return NodeSdk.layer(() => ({
+    resource: resource(),
+    spanProcessor: new SdkBase.BatchSpanProcessor(
+      new OTLP.OTLPTraceExporter({
+        url: `${endpoint}/v1/traces`,
+        headers,
+      }),
+    ),
+  }))
+}
+
+export * as Otlp from "./otlp"

+ 1 - 0
packages/core/src/observability/shared.ts

@@ -0,0 +1 @@
+export const runID = crypto.randomUUID().slice(0, 8)

+ 1 - 3
packages/core/src/project-reference.ts

@@ -103,9 +103,7 @@ export const layer = Layer.effect(
       (materializer) =>
         materializer.run.pipe(
           Effect.catchCause((cause) =>
-            Effect.logWarning("failed to materialize project reference").pipe(
-              Effect.annotateLogs({ name: materializer.name, repository: materializer.repository, cause }),
-            ),
+            Effect.logWarning("failed to materialize project reference", { name: materializer.name, repository: materializer.repository, cause }),
           ),
         ),
       { concurrency: 4, discard: true },

+ 4 - 7
packages/core/src/pty.ts

@@ -7,9 +7,7 @@ import { Location } from "./location"
 import { NonNegativeInt, PositiveInt } from "./schema"
 import { PtyID } from "./pty/schema"
 import { lazy } from "./util/lazy"
-import * as Log from "./util/log"
 
-const log = Log.create({ service: "pty" })
 const BUFFER_LIMIT = 1024 * 1024 * 2
 const BUFFER_CHUNK = 64 * 1024
 const encoder = new TextEncoder()
@@ -158,7 +156,7 @@ export const layer = Layer.effect(
       const session = sessions.get(id)
       if (!session) return false
       sessions.delete(id)
-      log.info("removing session", { id })
+      yield* Effect.logInfo("removing session", { id })
       teardown(session)
       yield* events.publish(Event.Deleted, { id: session.info.id })
       return true
@@ -179,7 +177,7 @@ export const layer = Layer.effect(
 
     const create = Effect.fn("Pty.create")(function* (input: PreparedCreate) {
       const id = PtyID.ascending()
-      log.info("creating session", { id, cmd: input.command, args: input.args, cwd: input.cwd })
+      yield* Effect.logInfo("creating session", { id, cmd: input.command, args: input.args, cwd: input.cwd })
       const { spawn } = yield* Effect.promise(() => pty())
       const proc = yield* Effect.sync(() =>
         spawn(input.command, input.args, {
@@ -231,7 +229,7 @@ export const layer = Layer.effect(
           if (session.info.status === "exited") return
           runFork(
             Effect.gen(function* () {
-              log.info("session exited", { id, exitCode })
+              yield* Effect.logInfo("session exited", { id, exitCode })
               session.info.status = "exited"
               yield* events.publish(Event.Exited, { id, exitCode })
               yield* removeSession(id)
@@ -263,7 +261,7 @@ export const layer = Layer.effect(
 
     const connect = Effect.fn("Pty.connect")(function* (id: PtyID, ws: Socket, cursor?: number) {
       const session = yield* requireSession(id).pipe(Effect.tapError(() => Effect.sync(() => ws.close())))
-      log.info("client connected to session", { id, directory: location.directory })
+      yield* Effect.logInfo("client connected to session", { id, directory: location.directory })
       const sub = sock(ws)
       session.subscribers.delete(sub)
       session.subscribers.set(sub, ws)
@@ -299,7 +297,6 @@ export const layer = Layer.effect(
           session.process.write(typeof message === "string" ? message : new TextDecoder().decode(message))
         },
         onClose: () => {
-          log.info("client disconnected from session", { id })
           cleanup()
         },
       }

+ 1 - 1
packages/core/src/session/logging.ts

@@ -5,4 +5,4 @@ export const logFailure = (
   message: "Failed to drain Session" | "Failed to wake Session",
   sessionID: SessionSchema.ID,
   cause: Cause.Cause<unknown>,
-) => Effect.logError(message, cause).pipe(Effect.annotateLogs("sessionID", sessionID))
+) => Effect.logError(message, cause).pipe(Effect.annotateLogs({ sessionID }))

+ 4 - 11
packages/core/src/skill/discovery.ts

@@ -6,7 +6,6 @@ import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } fr
 import { FSUtil } from "../fs-util"
 import { Global } from "../global"
 import { AbsolutePath } from "../schema"
-import * as Log from "../util/log"
 
 const skillConcurrency = 4
 const fileConcurrency = 8
@@ -71,7 +70,6 @@ export const layer = Layer.effect(
   Effect.gen(function* () {
     const fs = yield* FSUtil.Service
     const global = yield* Global.Service
-    const log = Log.create({ service: "skill-discovery" })
     const http = (yield* HttpClient.HttpClient).pipe(
       HttpClient.retryTransient({
         retryOn: "errors-and-responses",
@@ -87,7 +85,7 @@ export const layer = Layer.effect(
         http.execute,
         Effect.flatMap((response) => response.arrayBuffer),
         Effect.flatMap((body) => fs.writeWithDirs(destination, new Uint8Array(body))),
-        Effect.catch((error) => Effect.sync(() => log.error("failed to download skill file", { url, error }))),
+        Effect.catch((error) => Effect.logError("failed to download skill file", { url, error })),
       )
     })
 
@@ -100,10 +98,9 @@ export const layer = Layer.effect(
           HttpClientRequest.acceptJson,
           http.execute,
           Effect.flatMap(HttpClientResponse.schemaBodyJson(Index)),
-          Effect.catch((error) => {
-            log.error("failed to fetch skill index", { url: index, error })
-            return Effect.succeed(undefined)
-          }),
+          Effect.catch((error) =>
+            Effect.logError("failed to fetch skill index", { url: index, error }).pipe(Effect.as(undefined)),
+          ),
         )
         if (!data) return []
 
@@ -111,17 +108,14 @@ export const layer = Layer.effect(
         return yield* Effect.forEach(
           data.skills.flatMap((skill) => {
             if (!isSafeSegment(skill.name)) {
-              log.warn("skill entry has unsafe name", { url: index, skill: skill.name })
               return []
             }
             if (!skill.files.includes("SKILL.md") && !skill.files.includes(`${skill.name}.md`)) {
-              log.warn("skill entry missing Markdown definition", { url: index, skill: skill.name })
               return []
             }
 
             const root = path.resolve(sourceRoot, skill.name)
             if (!FSUtil.contains(sourceRoot, root) || root === sourceRoot) {
-              log.warn("skill entry escapes cache root", { url: index, skill: skill.name })
               return []
             }
 
@@ -144,7 +138,6 @@ export const layer = Layer.effect(
               }
             })
             if (files.some((file) => file === undefined)) {
-              log.warn("skill entry has unsafe file", { url: index, skill: skill.name })
               return []
             }
             return [{ skill, root, files: files as { url: string; destination: string }[] }]

+ 0 - 197
packages/core/src/util/log.ts

@@ -1,197 +0,0 @@
-export * as Log from "./log"
-
-import path from "path"
-import fs from "fs/promises"
-import { createWriteStream } from "fs"
-import * as Global from "../global"
-import { Schema } from "effect"
-import { Glob } from "./glob"
-
-export const Level = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({
-  identifier: "LogLevel",
-  description: "Log level",
-})
-export type Level = Schema.Schema.Type<typeof Level>
-
-const levelPriority: Record<Level, number> = {
-  DEBUG: 0,
-  INFO: 1,
-  WARN: 2,
-  ERROR: 3,
-}
-const keep = 10
-const initializedRunID = "OPENCODE_LOG_INITIALIZED_RUN_ID"
-
-let level: Level = "INFO"
-
-function shouldLog(input: Level): boolean {
-  return levelPriority[input] >= levelPriority[level]
-}
-
-export type Logger = {
-  debug(message?: any, extra?: Record<string, any>): void
-  info(message?: any, extra?: Record<string, any>): void
-  error(message?: any, extra?: Record<string, any>): void
-  warn(message?: any, extra?: Record<string, any>): void
-  tag(key: string, value: string): Logger
-  clone(): Logger
-  time(
-    message: string,
-    extra?: Record<string, any>,
-  ): {
-    stop(): void
-    [Symbol.dispose](): void
-  }
-}
-
-const loggers = new Map<string, Logger>()
-
-export const Default = create({ service: "default" })
-
-export interface Options {
-  print: boolean
-  dev?: boolean
-  level?: Level
-}
-
-let logpath = ""
-export function file() {
-  return logpath
-}
-export function getLevel(): Level {
-  return level
-}
-let write = (msg: any) => {
-  process.stderr.write(msg)
-  return msg.length
-}
-
-export async function init(options: Options) {
-  if (options.level) level = options.level
-  void cleanup(Global.Path.log)
-  if (options.print) return
-  logpath = path.join(
-    Global.Path.log,
-    options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
-  )
-  const runID = process.env.OPENCODE_RUN_ID
-  const shouldTruncate = !options.dev || !runID || process.env[initializedRunID] !== runID
-  if (shouldTruncate) await fs.truncate(logpath).catch(() => {})
-  if (options.dev && runID) process.env[initializedRunID] = runID
-  const stream = createWriteStream(logpath, { flags: "a" })
-  write = async (msg: any) => {
-    return new Promise((resolve, reject) => {
-      stream.write(msg, (err) => {
-        if (err) reject(err)
-        else resolve(msg.length)
-      })
-    })
-  }
-}
-
-async function cleanup(dir: string) {
-  const files = (
-    await Glob.scan("????-??-??T??????.log", {
-      cwd: dir,
-      absolute: false,
-      include: "file",
-    }).catch(() => [])
-  )
-    .filter((file) => path.basename(file) === file)
-    .sort()
-  if (files.length <= keep) return
-
-  const doomed = files.slice(0, -keep)
-  await Promise.all(doomed.map((file) => fs.unlink(path.join(dir, file)).catch(() => {})))
-}
-
-function formatError(error: Error, depth = 0): string {
-  const result = error.message
-  return error.cause instanceof Error && depth < 10
-    ? result + " Caused by: " + formatError(error.cause, depth + 1)
-    : result
-}
-
-let last = Date.now()
-export function create(tags?: Record<string, any>) {
-  tags = tags || {}
-
-  const service = tags["service"]
-  if (service && typeof service === "string") {
-    const cached = loggers.get(service)
-    if (cached) {
-      return cached
-    }
-  }
-
-  function build(message: any, extra?: Record<string, any>) {
-    const prefix = Object.entries({
-      ...tags,
-      ...extra,
-    })
-      .filter(([_, value]) => value !== undefined && value !== null)
-      .map(([key, value]) => {
-        const prefix = `${key}=`
-        if (value instanceof Error) return prefix + formatError(value)
-        if (typeof value === "object") return prefix + JSON.stringify(value)
-        return prefix + value
-      })
-      .join(" ")
-    const next = new Date()
-    const diff = next.getTime() - last
-    last = next.getTime()
-    return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n"
-  }
-  const result: Logger = {
-    debug(message?: any, extra?: Record<string, any>) {
-      if (shouldLog("DEBUG")) {
-        write("DEBUG " + build(message, extra))
-      }
-    },
-    info(message?: any, extra?: Record<string, any>) {
-      if (shouldLog("INFO")) {
-        write("INFO  " + build(message, extra))
-      }
-    },
-    error(message?: any, extra?: Record<string, any>) {
-      if (shouldLog("ERROR")) {
-        write("ERROR " + build(message, extra))
-      }
-    },
-    warn(message?: any, extra?: Record<string, any>) {
-      if (shouldLog("WARN")) {
-        write("WARN  " + build(message, extra))
-      }
-    },
-    tag(key: string, value: string) {
-      if (tags) tags[key] = value
-      return result
-    },
-    clone() {
-      return create({ ...tags })
-    },
-    time(message: string, extra?: Record<string, any>) {
-      const now = Date.now()
-      result.info(message, { status: "started", ...extra })
-      function stop() {
-        result.info(message, {
-          status: "completed",
-          duration: Date.now() - now,
-          ...extra,
-        })
-      }
-      return {
-        stop,
-        [Symbol.dispose]() {
-          stop()
-        },
-      }
-    },
-  }
-
-  if (service && typeof service === "string") {
-    loggers.set(service, result)
-  }
-
-  return result
-}

+ 0 - 24
packages/core/src/util/opencode-process.ts

@@ -1,24 +0,0 @@
-export const OPENCODE_RUN_ID = "OPENCODE_RUN_ID"
-export const OPENCODE_PROCESS_ROLE = "OPENCODE_PROCESS_ROLE"
-
-export function ensureRunID() {
-  return (process.env[OPENCODE_RUN_ID] ??= crypto.randomUUID())
-}
-
-export function ensureProcessRole(fallback: "main" | "worker") {
-  return (process.env[OPENCODE_PROCESS_ROLE] ??= fallback)
-}
-
-export function ensureProcessMetadata(fallback: "main" | "worker") {
-  return {
-    runID: ensureRunID(),
-    processRole: ensureProcessRole(fallback),
-  }
-}
-
-export function sanitizedProcessEnv(overrides?: Record<string, string>) {
-  const env = Object.fromEntries(
-    Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
-  )
-  return overrides ? Object.assign(env, overrides) : env
-}

+ 64 - 1
packages/core/test/effect/observability.test.ts

@@ -1,5 +1,11 @@
 import { afterEach, describe, expect, test } from "bun:test"
-import { resource } from "@opencode-ai/core/effect/observability"
+import { NodeFileSystem } from "@effect/platform-node"
+import { Effect, Layer, Logger } from "effect"
+import fs from "fs/promises"
+import os from "os"
+import path from "path"
+import { fileLogger } from "../../src/observability/logging"
+import { resource } from "../../src/observability/otlp"
 
 const otelResourceAttributes = process.env.OTEL_RESOURCE_ATTRIBUTES
 const opencodeClient = process.env.OPENCODE_CLIENT
@@ -42,5 +48,62 @@ describe("resource", () => {
       "service.namespace": "anomalyco",
     })
     expect(resource().attributes["service.instance.id"]).not.toBe("override")
+    expect(resource().attributes["opencode.run"]).toMatch(/^[0-9a-f]{8}$/)
   })
 })
+
+test("file logger appends concurrent runs with a run on every line", async () => {
+  const dir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-log-test-"))
+  await using _ = {
+    async [Symbol.asyncDispose]() {
+      await fs.rm(dir, { recursive: true, force: true })
+    },
+  }
+  const file = path.join(dir, "opencode.log")
+  const write = (runID: string) =>
+    Effect.forEach(
+      Array.from({ length: 50 }, (_, index) => index),
+      (index) => Effect.logInfo(`entry-${index}`),
+    ).pipe(
+      Effect.provide(Logger.layer([fileLogger(file, runID)]).pipe(Layer.provide(NodeFileSystem.layer), Layer.orDie)),
+      Effect.scoped,
+    )
+
+  await Effect.runPromise(Effect.all([write("run-a"), write("run-b")], { concurrency: "unbounded" }))
+
+  const lines = (await Bun.file(file).text()).trim().split("\n")
+  expect(lines).toHaveLength(100)
+  expect(lines.filter((line) => line.includes("run=run-a"))).toHaveLength(50)
+  expect(lines.filter((line) => line.includes("run=run-b"))).toHaveLength(50)
+  expect(lines.every((line) => line.startsWith("timestamp=") && line.includes(" level=INFO "))).toBe(true)
+  expect(lines.every((line) => !line.includes(" fiber="))).toBe(true)
+  expect(lines.every((line) => !line.startsWith("{"))).toBe(true)
+})
+
+test("file logger flattens nested objects", async () => {
+  const dir = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-log-test-"))
+  await using _ = {
+    async [Symbol.asyncDispose]() {
+      await fs.rm(dir, { recursive: true, force: true })
+    },
+  }
+  const file = path.join(dir, "opencode.log")
+
+  await Effect.logInfo("request complete", {
+    request: { method: "GET", timing: { duration: 42 } },
+    tags: ["api", "test"],
+  }).pipe(
+    Effect.annotateLogs({ session: { id: "session-1" } }),
+    Effect.provide(Logger.layer([fileLogger(file, "run-a")]).pipe(Layer.provide(NodeFileSystem.layer), Layer.orDie)),
+    Effect.scoped,
+    Effect.runPromise,
+  )
+
+  const line = (await Bun.file(file).text()).trim()
+  expect(line).toContain('message="request complete"')
+  expect(line).toContain("request.method=GET")
+  expect(line).toContain("request.timing.duration=42")
+  expect(line).toContain('tags="[\\\"api\\\",\\\"test\\\"]"')
+  expect(line).toContain("session.id=session-1")
+  expect(line).not.toContain("request={")
+})

+ 0 - 3
packages/desktop/src/main/env.d.ts

@@ -15,8 +15,5 @@ declare module "virtual:opencode-server" {
     export const get: typeof import("../../../opencode/dist/types/src/node").Config.get
     export type Info = import("../../../opencode/dist/types/src/node").Config.Info
   }
-  export namespace Log {
-    export const init: typeof import("../../../opencode/dist/types/src/node").Log.init
-  }
   export const bootstrap: typeof import("../../../opencode/dist/types/src/node").bootstrap
 }

+ 1 - 2
packages/desktop/src/main/sidecar.ts

@@ -54,8 +54,7 @@ async function start(command: StartCommand) {
     ensureLoopbackNoProxy()
     useSystemCertificates()
     useEnvProxy()
-    const { Log, Server } = await import("virtual:opencode-server")
-    await Log.init({ level: "WARN" })
+    const { Server } = await import("virtual:opencode-server")
 
     listener = await Server.listen({
       port: command.port,

+ 3 - 12
packages/opencode/src/acp/event.ts

@@ -1,5 +1,4 @@
 import type { AgentSideConnection } from "@agentclientprotocol/sdk"
-import * as Log from "@opencode-ai/core/util/log"
 import type {
   Event,
   EventMessagePartDelta,
@@ -22,8 +21,6 @@ import {
   completedToolUpdate,
 } from "./tool"
 
-const log = Log.create({ service: "acp-event" })
-
 type Connection = Pick<AgentSideConnection, "sessionUpdate"> &
   Partial<Pick<AgentSideConnection, "requestPermission" | "writeTextFile">>
 type GlobalEventEnvelope = {
@@ -59,9 +56,8 @@ export class Subscription {
   start() {
     if (this.started) return
     this.started = true
-    this.run().catch((error: unknown) => {
+    this.run().catch(() => {
       if (this.abort.signal.aborted) return
-      log.error("event subscription failed", { error })
     })
   }
 
@@ -125,9 +121,7 @@ export class Subscription {
       for await (const event of events.stream) {
         if (this.abort.signal.aborted) return
         if (!event.payload) continue
-        await this.handle(event.payload).catch((error: unknown) => {
-          log.error("failed to handle event", { error, type: event.payload?.type })
-        })
+        await this.handle(event.payload).catch(() => {})
       }
       if (!this.abort.signal.aborted) await new Promise((resolve) => setTimeout(resolve, 1000))
     }
@@ -214,10 +208,7 @@ export class Subscription {
         { throwOnError: true },
       )
       .then((response) => response.data)
-      .catch((error: unknown) => {
-        log.error("unexpected error when fetching message for delta metadata", { error, messageId, partId })
-        return undefined
-      })
+      .catch(() => undefined)
     if (!message) return
 
     const part = message.parts.find((item) => item.id === partId)

+ 3 - 24
packages/opencode/src/acp/permission.ts

@@ -1,5 +1,4 @@
 import type { AgentSideConnection, PermissionOption, RequestPermissionResponse } from "@agentclientprotocol/sdk"
-import * as Log from "@opencode-ai/core/util/log"
 import type { Event, OpencodeClient } from "@opencode-ai/sdk/v2"
 import { applyPatch } from "diff"
 import { exists, readText } from "@/util/filesystem"
@@ -7,8 +6,6 @@ import type { ACPSession } from "./session"
 import { toLocations, toToolKind, type ToolInput } from "./tool"
 import { Effect } from "effect"
 
-const log = Log.create({ service: "acp-permission" })
-
 type PermissionEvent = Extract<Event, { type: "permission.asked" }>
 type Reply = "once" | "always" | "reject"
 type Connection = Partial<Pick<AgentSideConnection, "requestPermission" | "writeTextFile">>
@@ -35,9 +32,7 @@ export class Handler {
     const previous = this.queues.get(permission.sessionID) ?? Promise.resolve()
     const next = previous
       .then(() => this.process(event))
-      .catch((error: unknown) => {
-        log.error("failed to handle permission", { error, permissionID: permission.id })
-      })
+      .catch(() => {})
       .finally(() => {
         if (this.queues.get(permission.sessionID) === next) {
           this.queues.delete(permission.sessionID)
@@ -52,10 +47,6 @@ export class Handler {
     if (!session) return
 
     if (!this.input.connection.requestPermission) {
-      log.error("ACP connection cannot request permission", {
-        permissionID: permission.id,
-        sessionID: permission.sessionID,
-      })
       await this.reply(permission.id, "reject", session.cwd)
       return
     }
@@ -73,12 +64,7 @@ export class Handler {
         },
         options: permissionOptions,
       })
-      .catch(async (error: unknown) => {
-        log.error("failed to request permission from ACP", {
-          error,
-          permissionID: permission.id,
-          sessionID: permission.sessionID,
-        })
+      .catch(async () => {
         await this.reply(permission.id, "reject", session.cwd)
         return undefined
       })
@@ -92,13 +78,7 @@ export class Handler {
     }
 
     if (permission.permission === "edit") {
-      await this.writeProposedEdit(session.id, permission.metadata).catch((error: unknown) => {
-        log.error("failed to write proposed edit through ACP", {
-          error,
-          permissionID: permission.id,
-          sessionID: permission.sessionID,
-        })
-      })
+      await this.writeProposedEdit(session.id, permission.metadata).catch(() => {})
     }
 
     await this.reply(permission.id, reply, session.cwd)
@@ -120,7 +100,6 @@ export class Handler {
     const content = (await exists(filepath)) ? await readText(filepath) : ""
     const next = applyPatch(content, diff)
     if (next === false) {
-      log.error("Failed to apply unified diff (context mismatch)")
       return
     }
 

+ 5 - 19
packages/opencode/src/acp/service.ts

@@ -30,7 +30,6 @@ import {
   type SetSessionModeResponse,
 } from "@agentclientprotocol/sdk"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
-import * as Log from "@opencode-ai/core/util/log"
 import type { Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
 import { Context, Effect, Layer, ManagedRuntime } from "effect"
 import * as ACPError from "./error"
@@ -47,7 +46,6 @@ import { Provider } from "@/provider/provider"
 import type { Command } from "@/command"
 
 export const AuthMethodID = "opencode-login"
-const log = Log.create({ service: "acp-service" })
 
 export type Error = ACPError.Error
 type ServiceConnection = Pick<AgentSideConnection, "sessionUpdate"> &
@@ -333,9 +331,7 @@ export function make(input: {
       "session",
     ).pipe(
       Effect.catch((error) =>
-        Effect.sync(() => {
-          log.error("failed to abort ACP backing session", { error, sessionID: current.id })
-        }),
+        Effect.logError("failed to abort ACP backing session", { error: error, sessionID: current.id }),
       ),
     )
   })
@@ -610,10 +606,7 @@ function makeUsageService(sdk: OpencodeClient) {
           ) as Record<ProviderV2.ID, Provider.Info>
           return UsageService.findContextLimit(providers, params.providerID, params.modelID)
         })
-        .catch((error: unknown) => {
-          log.error("failed to get providers for usage context limit", { error })
-          return undefined
-        })
+        .catch(() => undefined)
       limits.set(key, next)
       return yield* Effect.promise(() => next)
     },
@@ -633,10 +626,7 @@ function makeUsageService(sdk: OpencodeClient) {
     ).pipe(
       Effect.map((messages) => messages as readonly UsageService.SessionMessage[]),
       Effect.catch((error) =>
-        Effect.sync(() => {
-          log.error("failed to fetch messages for usage update", { error })
-          return undefined
-        }),
+        Effect.logError("failed to fetch messages for usage update", { error: error }).pipe(Effect.as(undefined)),
       ),
     )
     if (!messages) return
@@ -662,9 +652,7 @@ function makeUsageService(sdk: OpencodeClient) {
             cost: { amount: UsageService.totalSessionCost(messages), currency: "USD" },
           },
         })
-        .catch((error) => {
-          log.error("failed to send usage update", { error })
-        }),
+        .catch(() => {}),
     )
   })
 
@@ -681,9 +669,7 @@ function replayMessages(subscription: ACPEvent.Subscription | undefined, message
   if (!subscription) return Effect.void
   return Effect.promise(async () => {
     for (const message of messages) {
-      await subscription.replayMessage(message).catch((error: unknown) => {
-        log.error("failed to replay ACP message", { error, messageID: message.info.id })
-      })
+      await subscription.replayMessage(message).catch(() => {})
     }
   })
 }

+ 3 - 14
packages/opencode/src/acp/usage.ts

@@ -1,5 +1,4 @@
 import type { AgentSideConnection, Usage } from "@agentclientprotocol/sdk"
-import * as Log from "@opencode-ai/core/util/log"
 import type { AssistantMessage as OpenCodeAssistantMessage, Message } from "@opencode-ai/sdk/v2"
 import { InstanceRef } from "@/effect/instance-ref"
 import { InstanceStore } from "@/project/instance-store"
@@ -8,8 +7,6 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { Provider } from "@/provider/provider"
 import { Context, Effect, Layer, SynchronizedRef } from "effect"
 
-const log = Log.create({ service: "acp-usage" })
-
 export type AssistantTokenCost = Pick<OpenCodeAssistantMessage, "cost" | "tokens">
 
 export type AssistantMessage = AssistantTokenCost &
@@ -157,10 +154,7 @@ export const layer = Layer.effect(
             contextLimitLoader.providers(input.directory).pipe(
               Effect.map((providers) => findContextLimit(providers, input.providerID, input.modelID)),
               Effect.catch((error) =>
-                Effect.sync(() => {
-                  log.error("failed to get providers for usage context limit", { error })
-                  return undefined
-                }),
+                Effect.logError("failed to get providers for usage context limit", { error: error }).pipe(Effect.as(undefined)),
               ),
             ),
           )
@@ -184,10 +178,7 @@ export const layer = Layer.effect(
     }) {
       const messages = yield* messageLoader.messages({ sessionID: input.sessionID, directory: input.directory }).pipe(
         Effect.catch((error) =>
-          Effect.sync(() => {
-            log.error("failed to fetch messages for usage update", { error })
-            return undefined
-          }),
+          Effect.logError("failed to fetch messages for usage update", { error: error }).pipe(Effect.as(undefined)),
         ),
       )
       if (!messages) return
@@ -214,9 +205,7 @@ export const layer = Layer.effect(
               cost: { amount: totalSessionCost(messages), currency: "USD" },
             },
           })
-          .catch((error) => {
-            log.error("failed to send usage update", { error })
-          }),
+          .catch(() => {}),
       )
     })
 

+ 1 - 4
packages/opencode/src/cli/cmd/acp.ts

@@ -1,4 +1,3 @@
-import * as Log from "@opencode-ai/core/util/log"
 import { Effect } from "effect"
 import { effectCmd } from "../effect-cmd"
 import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
@@ -7,8 +6,6 @@ import { createOpencodeClient } from "@opencode-ai/sdk/v2"
 import { withNetworkOptions, resolveNetworkOptions } from "../network"
 import { ACPProfile } from "@/acp/profile"
 
-const log = Log.create({ service: "acp-command" })
-
 export const AcpCommand = effectCmd({
   command: "acp",
   describe: "start ACP (Agent Client Protocol) server",
@@ -63,7 +60,7 @@ export const AcpCommand = effectCmd({
       return agent.create(conn)
     }, stream)
 
-    log.info("setup connection")
+    yield* Effect.logInfo("setup connection")
     process.stdin.resume()
     yield* Effect.promise(
       () =>

+ 2 - 3
packages/opencode/src/cli/cmd/debug/lsp.ts

@@ -2,7 +2,6 @@ import { LSP } from "@/lsp/lsp"
 import { Effect } from "effect"
 import { effectCmd } from "../../effect-cmd"
 import { cmd } from "../cmd"
-import * as Log from "@opencode-ai/core/util/log"
 import { EOL } from "os"
 
 export const LSPCommand = cmd({
@@ -33,7 +32,7 @@ export const SymbolsCommand = effectCmd({
   describe: "search workspace symbols",
   builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
   handler: Effect.fn("Cli.debug.lsp.symbols")(function* (args) {
-    using _ = Log.Default.time("symbols")
+    yield* Effect.logInfo("symbols")
     const results = yield* LSP.Service.use((lsp) => lsp.workspaceSymbol(args.query))
     process.stdout.write(JSON.stringify(results, null, 2) + EOL)
   }),
@@ -44,7 +43,7 @@ export const DocumentSymbolsCommand = effectCmd({
   describe: "get symbols from a document",
   builder: (yargs) => yargs.positional("uri", { type: "string", demandOption: true }),
   handler: Effect.fn("Cli.debug.lsp.documentSymbols")(function* (args) {
-    using _ = Log.Default.time("document-symbols")
+    yield* Effect.logInfo("document-symbols")
     const results = yield* LSP.Service.use((lsp) => lsp.documentSymbol(args.uri))
     process.stdout.write(JSON.stringify(results, null, 2) + EOL)
   }),

+ 0 - 3
packages/opencode/src/cli/cmd/debug/scrap.ts

@@ -1,5 +1,4 @@
 import { EOL } from "os"
-import * as Log from "@opencode-ai/core/util/log"
 import { cmd } from "../cmd"
 
 export const ScrapCommand = cmd({
@@ -10,9 +9,7 @@ export const ScrapCommand = cmd({
     const { Project } = await import("@/project/project")
     const { makeRuntime } = await import("@opencode-ai/core/effect/runtime")
     const runtime = makeRuntime(Project.Service, Project.defaultLayer)
-    const timer = Log.Default.time("scrap")
     const list = await runtime.runPromise((project) => project.list())
     process.stdout.write(JSON.stringify(list, null, 2) + EOL)
-    timer.stop()
   },
 })

+ 6 - 30
packages/opencode/src/cli/cmd/run/footer.ts

@@ -30,7 +30,6 @@ import { render } from "@opentui/solid"
 import { createComponent, createSignal, type Accessor, type Setter } from "solid-js"
 import { createStore, reconcile } from "solid-js/store"
 import { OpencodeKeymapProvider } from "@opencode-ai/tui/keymap"
-import { withRunSpan } from "./otel"
 import { RUN_COMMAND_PANEL_ROWS, RUN_SUBAGENT_PANEL_ROWS } from "./footer.command"
 import { SUBAGENT_INSPECTOR_ROWS } from "./footer.subagent"
 import { PROMPT_MAX_ROWS, TEXTAREA_MIN_ROWS } from "./footer.prompt"
@@ -513,20 +512,8 @@ export class RunFooter implements FooterApi {
   }
 
   private completeScrollback(): void {
-    const phase = this.state().phase
     this.flushing = this.flushing
-      .then(() =>
-        withRunSpan(
-          "RunFooter.completeScrollback",
-          {
-            "opencode.footer.phase": phase,
-            "session.id": this.options.sessionID() || undefined,
-          },
-          async () => {
-            await this.scrollback.complete()
-          },
-        ),
-      )
+      .then(() => this.scrollback.complete())
       .catch((error) => {
         this.flushError = error
       })
@@ -1129,23 +1116,12 @@ export class RunFooter implements FooterApi {
     }
 
     const batch = this.queue.splice(0)
-    const phase = this.state().phase
     this.flushing = this.flushing
-      .then(() =>
-        withRunSpan(
-          "RunFooter.flush",
-          {
-            "opencode.batch.commits": batch.length,
-            "opencode.footer.phase": phase,
-            "session.id": this.options.sessionID() || undefined,
-          },
-          async () => {
-            for (const item of batch) {
-              await this.scrollback.append(item)
-            }
-          },
-        ),
-      )
+      .then(async () => {
+        for (const item of batch) {
+          await this.scrollback.append(item)
+        }
+      })
       .catch((error) => {
         this.flushError = error
       })

+ 0 - 117
packages/opencode/src/cli/cmd/run/otel.ts

@@ -1,117 +0,0 @@
-import { INVALID_SPAN_CONTEXT, context, trace, SpanStatusCode, type Span } from "@opentelemetry/api"
-import { Effect, ManagedRuntime } from "effect"
-import { memoMap } from "@opencode-ai/core/effect/memo-map"
-import { Observability } from "@opencode-ai/core/effect/observability"
-
-type AttributeValue = string | number | boolean | undefined
-
-export type RunSpanAttributes = Record<string, AttributeValue>
-
-const noop = trace.wrapSpanContext(INVALID_SPAN_CONTEXT)
-const tracer = trace.getTracer("opencode.run")
-const runtime = ManagedRuntime.make(Observability.layer, { memoMap })
-let ready: Promise<void> | undefined
-
-function attributes(input?: RunSpanAttributes): Record<string, string | number | boolean> | undefined {
-  if (!input) {
-    return undefined
-  }
-
-  const out = Object.entries(input).flatMap(([key, value]) => (value === undefined ? [] : [[key, value] as const]))
-  if (out.length === 0) {
-    return undefined
-  }
-
-  return Object.fromEntries(out)
-}
-
-function message(error: unknown) {
-  if (typeof error === "string") {
-    return error
-  }
-
-  if (error instanceof Error) {
-    return error.message || error.name
-  }
-
-  return String(error)
-}
-
-function ensure() {
-  if (!Observability.enabled) {
-    return Promise.resolve()
-  }
-
-  if (ready) {
-    return ready
-  }
-
-  ready = runtime.runPromise(Effect.void).then(
-    () => undefined,
-    (error) => {
-      ready = undefined
-      throw error
-    },
-  )
-  return ready
-}
-
-function finish<A>(span: Span, out: Promise<A>) {
-  return out.then(
-    (value) => {
-      span.end()
-      return value
-    },
-    (error) => {
-      recordRunSpanError(span, error)
-      span.end()
-      throw error
-    },
-  )
-}
-
-export function setRunSpanAttributes(span: Span, input?: RunSpanAttributes): void {
-  const next = attributes(input)
-  if (!next) {
-    return
-  }
-
-  span.setAttributes(next)
-}
-
-export function recordRunSpanError(span: Span, error: unknown): void {
-  const next = message(error)
-  span.recordException(error instanceof Error ? error : next)
-  span.setStatus({
-    code: SpanStatusCode.ERROR,
-    message: next,
-  })
-}
-
-export function withRunSpan<A>(
-  name: string,
-  input: RunSpanAttributes | undefined,
-  fn: (span: Span) => Promise<A> | A,
-): A | Promise<A> {
-  if (!Observability.enabled) {
-    return fn(noop)
-  }
-
-  return ensure().then(
-    () => {
-      const span = tracer.startSpan(name, {
-        attributes: attributes(input),
-      })
-
-      return context.with(trace.setSpan(context.active(), span), () =>
-        finish(
-          span,
-          new Promise<A>((resolve) => {
-            resolve(fn(span))
-          }),
-        ),
-      )
-    },
-    () => fn(noop),
-  )
-}

+ 0 - 24
packages/opencode/src/cli/cmd/run/runtime.lifecycle.ts

@@ -16,7 +16,6 @@ import { openEditor } from "@opencode-ai/tui/editor"
 import { registerOpencodeKeymap } from "@opencode-ai/tui/keymap"
 import { Session as SessionApi } from "@/session/session"
 import * as Locale from "@/util/locale"
-import { withRunSpan } from "./otel"
 import { resolveInteractiveStdin } from "./runtime.stdin"
 import { entrySplash, exitSplash, splashMeta } from "./splash"
 import { resolveRunTheme } from "./theme"
@@ -175,18 +174,6 @@ function queueSplash(
 // scrollback commits and footer repaints happen in the same frame. After
 // the entry splash, RunFooter takes over the footer region.
 export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lifecycle> {
-  return withRunSpan(
-    "RunLifecycle.boot",
-    {
-      "opencode.agent.name": input.agent,
-      "opencode.directory": input.directory,
-      "opencode.first": input.first,
-      "opencode.model.provider": input.model?.providerID,
-      "opencode.model.id": input.model?.modelID,
-      "opencode.model.variant": input.variant,
-      "session.id": input.getSessionID?.() || input.sessionID || undefined,
-    },
-    async () => {
       const source = resolveInteractiveStdin()
       let unregisterKeymap: (() => void) | undefined
 
@@ -326,13 +313,6 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
           }
 
           closed = true
-          return withRunSpan(
-            "RunLifecycle.close",
-            {
-              "opencode.show_exit": next.showExit,
-              "session.id": next.sessionID || input.getSessionID?.() || input.sessionID || undefined,
-            },
-            async () => {
               detachSigint()
               let wroteExit = false
 
@@ -368,8 +348,6 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
                 }
                 source.cleanup?.()
               }
-            },
-          )
         }
 
         return {
@@ -425,6 +403,4 @@ export async function createRuntimeLifecycle(input: LifecycleInput): Promise<Lif
         source.cleanup?.()
         throw error
       }
-    },
-  )
 }

+ 1 - 89
packages/opencode/src/cli/cmd/run/runtime.ts

@@ -18,7 +18,6 @@ import { MessageID } from "@/session/schema"
 import { createRunDemo } from "./demo"
 import { resolveModelInfo, resolveRunTuiConfig, resolveSessionInfo } from "./runtime.boot"
 import { createRuntimeLifecycle } from "./runtime.lifecycle"
-import { recordRunSpanError, setRunSpanAttributes, withRunSpan } from "./otel"
 import { trace } from "./trace"
 import { cycleVariant, formatModelLabel, resolveSavedVariant, resolveVariant, saveVariant } from "./variant.shared"
 import type { LocalReplayAnchor, LocalReplayRow, RunInput, RunPrompt, RunProvider, StreamCommit } from "./types"
@@ -180,14 +179,6 @@ async function resolveExitTitle(
 // Files only attach on the first prompt turn -- after that, includeFiles
 // flips to false so subsequent turns don't re-send attachments.
 async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDeps = {}): Promise<void> {
-  return withRunSpan(
-    "RunInteractive.session",
-    {
-      "opencode.mode": input.resolveSession ? "local" : "attach",
-      "opencode.initial_input": !!input.initialInput,
-      "opencode.demo": input.demo,
-    },
-    async (span) => {
       const start = performance.now()
       const log = trace()
       const tuiConfigTask = resolveRunTuiConfig()
@@ -217,15 +208,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
         sessionTitle: ctx.sessionTitle,
         agent: ctx.agent,
       }
-      setRunSpanAttributes(span, {
-        "opencode.directory": ctx.directory,
-        "opencode.resume": ctx.resume === true,
-        "opencode.agent.name": state.agent,
-        "opencode.model.provider": state.model?.providerID,
-        "opencode.model.id": state.model?.modelID,
-        "opencode.model.variant": state.activeVariant,
-        "session.id": state.sessionID || undefined,
-      })
       const ensureSession = () => {
         if (!input.resolveSession || state.sessionID) {
           return Promise.resolve()
@@ -239,10 +221,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
           state.sessionID = next.sessionID
           state.sessionTitle = next.sessionTitle ?? state.sessionTitle
           state.agent = next.agent
-          setRunSpanAttributes(span, {
-            "opencode.agent.name": state.agent,
-            "session.id": state.sessionID,
-          })
         })
         return state.session
       }
@@ -297,9 +275,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
 
           state.activeVariant = cycleVariant(state.activeVariant, state.variants)
           saveVariant(state.model, state.activeVariant)
-          setRunSpanAttributes(span, {
-            "opencode.model.variant": state.activeVariant,
-          })
           return {
             status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default",
             modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers),
@@ -333,11 +308,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
             return
           }
 
-          setRunSpanAttributes(span, {
-            "opencode.model.provider": model.providerID,
-            "opencode.model.id": model.modelID,
-            "opencode.model.variant": state.activeVariant,
-          })
           return {
             modelLabel: formatModelLabel(model, state.activeVariant, state.providers),
             status: `model ${model.modelID}`,
@@ -360,9 +330,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
 
           state.activeVariant = variant
           saveVariant(state.model, state.activeVariant)
-          setRunSpanAttributes(span, {
-            "opencode.model.variant": state.activeVariant,
-          })
           return {
             status: state.activeVariant ? `variant ${state.activeVariant}` : "variant default",
             modelLabel: formatModelLabel(state.model, state.activeVariant, state.providers),
@@ -468,9 +435,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
         const next = resolveVariant(ctx.variant, session.variant, savedVariant, state.variants)
         if (next !== state.activeVariant) {
           state.activeVariant = next
-          setRunSpanAttributes(span, {
-            "opencode.model.variant": state.activeVariant,
-          })
         }
 
         if (footer.isClosed) {
@@ -624,13 +588,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
                         limits: () => state.limits,
                       })
                     : undefined
-                  setRunSpanAttributes(span, {
-                    "opencode.agent.name": state.agent,
-                    "opencode.model.provider": state.model?.providerID,
-                    "opencode.model.id": state.model?.modelID,
-                    "opencode.model.variant": state.activeVariant,
-                    "session.id": state.sessionID,
-                  })
                   log?.write("session.new", {
                     sessionID: state.sessionID,
                   })
@@ -688,29 +645,8 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
             await state.switching?.catch(() => {})
 
             let outputAnchor: LocalReplayAnchor | undefined
-            return withRunSpan(
-              "RunInteractive.turn",
-              {
-                "opencode.agent.name": state.agent,
-                "opencode.model.provider": state.model?.providerID,
-                "opencode.model.id": state.model?.modelID,
-                "opencode.model.variant": state.activeVariant,
-                "opencode.prompt.chars": prompt.text.length,
-                "opencode.prompt.parts": prompt.parts.length,
-                "opencode.prompt.include_files": includeFiles,
-                "opencode.prompt.file_parts": includeFiles ? input.files.length : 0,
-                "session.id": state.sessionID || undefined,
-              },
-              async (span) => {
                 try {
                   const next = await ensureStream()
-                  setRunSpanAttributes(span, {
-                    "opencode.agent.name": state.agent,
-                    "opencode.model.provider": state.model?.providerID,
-                    "opencode.model.id": state.model?.modelID,
-                    "opencode.model.variant": state.activeVariant,
-                    "session.id": state.sessionID || undefined,
-                  })
                   await next.handle.runPromptTurn({
                     agent: state.agent,
                     model: state.model,
@@ -734,7 +670,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
                     return
                   }
 
-                  recordRunSpanError(span, error)
                   const text =
                     (await state.stream?.then((item) => item.mod).catch(() => undefined))?.formatUnknownError(error) ??
                     (error instanceof Error ? error.message : String(error))
@@ -748,8 +683,6 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
                   rememberLocal(commit, outputAnchor)
                   footer.append(commit)
                 }
-              },
-            )
           },
         })
       }
@@ -795,21 +728,11 @@ async function runInteractiveRuntime(input: RunRuntimeInput, deps: RunRuntimeDep
           history: state.history,
         })
       }
-    },
-  )
 }
 
 // Local in-process mode. Creates an SDK client backed by a direct fetch to
 // the in-process server, so no external HTTP server is needed.
 export async function runInteractiveLocalMode(input: RunLocalInput): Promise<void> {
-  return withRunSpan(
-    "RunInteractive.localMode",
-    {
-      "opencode.directory": input.directory,
-      "opencode.initial_input": !!input.initialInput,
-      "opencode.demo": input.demo,
-    },
-    async () => {
       const sdk = createOpencodeClient({
         baseUrl: "http://opencode.internal",
         fetch: input.fetch,
@@ -858,8 +781,6 @@ export async function runInteractiveLocalMode(input: RunLocalInput): Promise<voi
           }
         },
       })
-    },
-  )
 }
 
 // Attach mode. Uses the caller-provided SDK client directly.
@@ -867,15 +788,7 @@ export async function runInteractiveMode(
   input: RunInput & { createSession?: CreateSession },
   deps?: RunRuntimeDeps,
 ): Promise<void> {
-  return withRunSpan(
-    "RunInteractive.attachMode",
-    {
-      "opencode.directory": input.directory,
-      "opencode.initial_input": !!input.initialInput,
-      "session.id": input.sessionID,
-    },
-    async () =>
-      runInteractiveRuntime(
+  return runInteractiveRuntime(
         {
           files: input.files,
           initialInput: input.initialInput,
@@ -897,6 +810,5 @@ export async function runInteractiveMode(
           createSession: createSessionResolver(input.createSession),
         },
         deps,
-      ),
   )
 }

+ 1 - 12
packages/opencode/src/cli/cmd/run/scrollback.surface.ts

@@ -14,7 +14,6 @@ import {
   type ScrollbackSurface,
 } from "@opentui/core"
 import { entryBody, entryCanStream, entryDone, entryFlags } from "./entry.body"
-import { withRunSpan } from "./otel"
 import { entryColor, entryLook, entrySyntax } from "./scrollback.shared"
 import { turnSummaryCommit } from "./turn-summary"
 import { entryWriter, sameEntryGroup, separatorRows, spacerWriter, turnSummaryWriter } from "./scrollback.writer"
@@ -424,17 +423,7 @@ export class RunScrollbackStream {
   }
 
   public async complete(trailingNewline = false): Promise<void> {
-    return withRunSpan(
-      "RunScrollbackStream.complete",
-      {
-        "opencode.entry.active": !!this.active,
-        "opencode.trailing_newline": trailingNewline,
-        "session.id": this.sessionID?.() || undefined,
-      },
-      async () => {
-        this.markRendered(await this.finishActive(trailingNewline))
-      },
-    )
+    this.markRendered(await this.finishActive(trailingNewline))
   }
 
   public async writeTurnSummary(input: { agent: string; model: string; duration: string }): Promise<void> {

+ 4 - 44
packages/opencode/src/cli/cmd/tui.ts

@@ -4,7 +4,6 @@ import { type rpc } from "../tui/worker"
 import path from "path"
 import { fileURLToPath } from "url"
 import { UI } from "@/cli/ui"
-import * as Log from "@opencode-ai/core/util/log"
 import { errorMessage } from "@opencode-ai/tui/util/error"
 import { withTimeout } from "@/util/timeout"
 import { withNetworkOptions, resolveNetworkOptionsNoConfig } from "@/cli/network"
@@ -12,12 +11,6 @@ import { Filesystem } from "@/util/filesystem"
 import type { GlobalEvent } from "@opencode-ai/sdk/v2"
 import type { EventSource } from "@opencode-ai/tui/context/sdk"
 import { writeHeapSnapshot } from "v8"
-import {
-  OPENCODE_PROCESS_ROLE,
-  OPENCODE_RUN_ID,
-  ensureRunID,
-  sanitizedProcessEnv,
-} from "@opencode-ai/core/util/opencode-process"
 import { validateSession } from "../tui/validate-session"
 import { win32InstallCtrlCGuard } from "@opencode-ai/tui/terminal-win32"
 
@@ -132,51 +125,20 @@ export const TuiThreadCommand = cmd({
         return
       }
       const cwd = Filesystem.resolve(process.cwd())
-      const env = sanitizedProcessEnv({
-        [OPENCODE_PROCESS_ROLE]: "worker",
-        [OPENCODE_RUN_ID]: ensureRunID(),
-      })
-
-      const worker = new Worker(file, {
-        env,
-      })
-      worker.onerror = (e) => {
-        Log.Default.error("thread error", {
-          message: e.message,
-          filename: e.filename,
-          lineno: e.lineno,
-          colno: e.colno,
-          error: e.error,
-        })
-      }
 
+      const worker = new Worker(file)
       const client = Rpc.client<typeof rpc>(worker)
-      const error = (e: unknown) => {
-        Log.Default.error("process error", { error: errorMessage(e) })
-      }
       const reload = () => {
-        client.call("reload", undefined).catch((err) => {
-          Log.Default.warn("worker reload failed", {
-            error: errorMessage(err),
-          })
-        })
+        client.call("reload", undefined).catch(() => {})
       }
-      process.on("uncaughtException", error)
-      process.on("unhandledRejection", error)
       process.on("SIGUSR2", reload)
 
       let stopped = false
       const stop = async () => {
         if (stopped) return
         stopped = true
-        process.off("uncaughtException", error)
-        process.off("unhandledRejection", error)
         process.off("SIGUSR2", reload)
-        await withTimeout(client.call("shutdown", undefined), 5000).catch((error) => {
-          Log.Default.warn("worker shutdown failed", {
-            error: errorMessage(error),
-          })
-        })
+        await withTimeout(client.call("shutdown", undefined), 5000).catch(() => {})
         worker.terminate()
       }
 
@@ -255,9 +217,7 @@ export const TuiThreadCommand = cmd({
     } finally {
       try {
         unguard?.()
-      } catch (error) {
-        Log.Default.warn("failed to restore terminal guard", { error: errorMessage(error) })
-      }
+      } catch {}
     }
   },
 })

+ 1 - 15
packages/opencode/src/cli/heap.ts

@@ -2,9 +2,6 @@ import path from "path"
 import { writeHeapSnapshot } from "node:v8"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Global } from "@opencode-ai/core/global"
-import * as Log from "@opencode-ai/core/util/log"
-
-const log = Log.create({ service: "heap" })
 const MINUTE = 60_000
 const LIMIT = 2 * 1024 * 1024 * 1024
 
@@ -32,20 +29,9 @@ export function start() {
       Global.Path.log,
       `heap-${process.pid}-${new Date().toISOString().replace(/[:.]/g, "")}.heapsnapshot`,
     )
-    log.warn("heap usage exceeded limit", {
-      rss: stat.rss,
-      heap: stat.heapUsed,
-      file,
-    })
-
     await Promise.resolve()
       .then(() => writeHeapSnapshot(file))
-      .catch((err) => {
-        log.error("failed to write heap snapshot", {
-          error: err instanceof Error ? err.message : String(err),
-          file,
-        })
-      })
+      .catch(() => {})
 
     lock = false
   }

+ 0 - 28
packages/opencode/src/cli/tui/worker.ts

@@ -1,6 +1,4 @@
-import { Installation } from "@/installation"
 import { Server } from "@/server/server"
-import * as Log from "@opencode-ai/core/util/log"
 import { InstanceRuntime } from "@/project/instance-runtime"
 import { Rpc } from "@/util/rpc"
 import { upgrade } from "@/cli/upgrade"
@@ -10,35 +8,11 @@ import { ServerAuth } from "@/server/auth"
 import { writeHeapSnapshot } from "node:v8"
 import { Heap } from "@/cli/heap"
 import { AppRuntime } from "@/effect/app-runtime"
-import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process"
 import { Effect } from "effect"
 import { disposeAllInstancesAndEmitGlobalDisposed } from "@/server/global-lifecycle"
 
-ensureProcessMetadata("worker")
-
-await Log.init({
-  print: process.argv.includes("--print-logs"),
-  dev: Installation.isLocal(),
-  level: (() => {
-    if (Installation.isLocal()) return "DEBUG"
-    return "INFO"
-  })(),
-})
-
 Heap.start()
 
-process.on("unhandledRejection", (e) => {
-  Log.Default.error("rejection", {
-    e: e instanceof Error ? e.message : e,
-  })
-})
-
-process.on("uncaughtException", (e) => {
-  Log.Default.error("exception", {
-    e: e instanceof Error ? e.message : e,
-  })
-})
-
 // Subscribe to global events and forward them via RPC
 GlobalBus.on("event", (event) => {
   Rpc.emit("global.event", event)
@@ -89,8 +63,6 @@ export const rpc = {
     )
   },
   async shutdown() {
-    Log.Default.info("worker shutting down")
-
     await InstanceRuntime.disposeAllInstances()
     if (server) await server.stop(true)
   },

+ 2 - 11
packages/opencode/src/config/agent.ts

@@ -2,15 +2,12 @@ export * as ConfigAgent from "./agent"
 
 import path from "path"
 import { Exit, Schema } from "effect"
-import * as Log from "@opencode-ai/core/util/log"
 import { Glob } from "@opencode-ai/core/util/glob"
 import { ConfigAgentV1 } from "@opencode-ai/core/v1/config/agent"
 import { configEntryNameFromPath } from "./entry-name"
 import * as ConfigMarkdown from "./markdown"
 import { ConfigParse } from "./parse"
 
-const log = Log.create({ service: "config" })
-
 export async function load(dir: string) {
   const result: Record<string, ConfigAgentV1.Info> = {}
   for (const item of await Glob.scan("{agent,agents}/**/*.md", {
@@ -19,10 +16,7 @@ export async function load(dir: string) {
     dot: true,
     symlink: true,
   })) {
-    const md = await ConfigMarkdown.parse(item).catch((err) => {
-      log.error("failed to load agent", { agent: item, err })
-      return undefined
-    })
+    const md = await ConfigMarkdown.parse(item).catch(() => undefined)
     if (!md) continue
 
     const name = configEntryNameFromPath(path.relative(dir, item), ["agent/", "agents/"])
@@ -45,10 +39,7 @@ export async function loadMode(dir: string) {
     dot: true,
     symlink: true,
   })) {
-    const md = await ConfigMarkdown.parse(item).catch((err) => {
-      log.error("failed to load mode", { mode: item, err })
-      return undefined
-    })
+    const md = await ConfigMarkdown.parse(item).catch(() => undefined)
     if (!md) continue
 
     const config = {

+ 1 - 7
packages/opencode/src/config/command.ts

@@ -1,7 +1,6 @@
 export * as ConfigCommand from "./command"
 
 import path from "path"
-import * as Log from "@opencode-ai/core/util/log"
 import { Cause, Exit, Schema } from "effect"
 import { Glob } from "@opencode-ai/core/util/glob"
 import { ConfigCommandV1 } from "@opencode-ai/core/v1/config/command"
@@ -9,8 +8,6 @@ import { configEntryNameFromPath } from "./entry-name"
 import { InvalidError } from "@opencode-ai/core/v1/config/error"
 import * as ConfigMarkdown from "./markdown"
 
-const log = Log.create({ service: "config" })
-
 const decodeInfo = Schema.decodeUnknownExit(ConfigCommandV1.Info)
 
 export async function load(dir: string) {
@@ -21,10 +18,7 @@ export async function load(dir: string) {
     dot: true,
     symlink: true,
   })) {
-    const md = await ConfigMarkdown.parse(item).catch((err) => {
-      log.error("failed to load command", { command: item, err })
-      return undefined
-    })
+    const md = await ConfigMarkdown.parse(item).catch(() => undefined)
     if (!md) continue
 
     const name = configEntryNameFromPath(path.relative(dir, item), ["command/", "commands/"])

+ 18 - 25
packages/opencode/src/config/config.ts

@@ -1,4 +1,3 @@
-import * as Log from "@opencode-ai/core/util/log"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
 import path from "path"
 import { pathToFileURL } from "url"
@@ -34,8 +33,6 @@ import { ConfigVariable } from "./variable"
 import { Npm } from "@opencode-ai/core/npm"
 import { withTransientReadRetry } from "@/util/effect-http-client"
 
-const log = Log.create({ service: "config" })
-
 // Custom merge function that concatenates array fields instead of replacing them
 // Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here.
 function mergeConfig(target: Info, source: Info): Info {
@@ -50,7 +47,7 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info {
   return merged
 }
 
-function normalizeLoadedConfig(data: unknown, source: string) {
+function normalizeLoadedConfig(data: unknown) {
   if (!isRecord(data)) return data
   const copy = { ...data }
   const hadLegacy = "theme" in copy || "keybinds" in copy || "tui" in copy
@@ -58,7 +55,6 @@ function normalizeLoadedConfig(data: unknown, source: string) {
   delete copy.theme
   delete copy.keybinds
   delete copy.tui
-  log.warn("tui keys in opencode config are deprecated; move them to tui.json", { path: source })
   return copy
 }
 
@@ -216,7 +212,7 @@ export const layer = Layer.effect(
         ),
       )
       const parsed = ConfigParse.jsonc(expanded, source)
-      const data = ConfigParse.schema(ConfigV1.Info, normalizeLoadedConfig(parsed, source), source)
+      const data = ConfigParse.schema(ConfigV1.Info, normalizeLoadedConfig(parsed), source)
       if (!("path" in options)) return data
 
       yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
@@ -229,7 +225,7 @@ export const layer = Layer.effect(
     })
 
     const loadFile = Effect.fnUntraced(function* (filepath: string, env?: Record<string, string>) {
-      log.info("loading", { path: filepath })
+      yield* Effect.logInfo("loading", { path: filepath })
       const text = yield* readConfigFile(filepath)
       if (!text) return {} as Info
       return yield* loadConfig(text, { path: filepath }, env)
@@ -273,7 +269,7 @@ export const layer = Layer.effect(
     const [cachedGlobal, invalidateGlobal] = yield* Effect.cachedInvalidateWithTTL(
       loadGlobal().pipe(
         Effect.tapError((error) =>
-          Effect.sync(() => log.error("failed to load global config, using defaults", { error: String(error) })),
+          Effect.logError("failed to load global config, using defaults", { error: String(error) }),
         ),
         Effect.orElseSucceed((): Info => ({})),
       ),
@@ -349,7 +345,7 @@ export const layer = Layer.effect(
             const url = key.replace(/\/+$/, "")
             authEnv[value.key] = value.token
             const wellknownURL = `${url}/.well-known/opencode`
-            log.debug("fetching remote config", { url: wellknownURL })
+            yield* Effect.logDebug("fetching remote config", { url: wellknownURL })
             const wellknown = yield* fetchRemoteJson(wellknownURL, undefined, ConfigV1.WellKnown)
             const remote = yield* Effect.promise(() =>
               substituteWellKnownRemoteConfig({
@@ -361,7 +357,7 @@ export const layer = Layer.effect(
             )
             const fetchedConfig = remote
               ? yield* Effect.gen(function* () {
-                  log.debug("fetching remote config", { url: remote.url })
+                  yield* Effect.logDebug("fetching remote config", { url: remote.url })
                   const data = yield* fetchRemoteJson(remote.url, remote.headers, Schema.Json)
                   if (isRecord(data) && isRecord(data.config)) return data.config
                   if (isRecord(data)) return data
@@ -382,7 +378,7 @@ export const layer = Layer.effect(
               authEnv,
             )
             yield* merge(source, next, "global")
-            log.debug("loaded remote config from well-known", { url })
+            yield* Effect.logDebug("loaded remote config from well-known", { url })
           }
         }
 
@@ -391,7 +387,7 @@ export const layer = Layer.effect(
 
         if (Flag.OPENCODE_CONFIG) {
           yield* merge(Flag.OPENCODE_CONFIG, yield* loadFile(Flag.OPENCODE_CONFIG, authEnv))
-          log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
+          yield* Effect.logDebug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
         }
 
         if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
@@ -407,7 +403,7 @@ export const layer = Layer.effect(
         const directories = yield* ConfigPaths.directories(ctx.directory, ctx.worktree)
 
         if (Flag.OPENCODE_CONFIG_DIR) {
-          log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
+          yield* Effect.logDebug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
         }
 
         const deps: Fiber.Fiber<void>[] = []
@@ -416,7 +412,7 @@ export const layer = Layer.effect(
           if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
             for (const file of ["opencode.json", "opencode.jsonc"]) {
               const source = path.join(dir, file)
-              log.debug(`loading config from ${source}`)
+              yield* Effect.logDebug(`loading config from ${source}`)
               yield* merge(source, yield* loadFile(source, authEnv))
               result.agent ??= {}
               result.mode ??= {}
@@ -439,9 +435,7 @@ export const layer = Layer.effect(
               Effect.exit,
               Effect.tap((exit) =>
                 Exit.isFailure(exit)
-                  ? Effect.sync(() => {
-                      log.warn("background dependency install failed", { dir, error: String(exit.cause) })
-                    })
+                  ? Effect.logWarning("background dependency install failed", { dir, error: String(exit.cause) })
                   : Effect.void,
               ),
               Effect.asVoid,
@@ -465,7 +459,7 @@ export const layer = Layer.effect(
             source,
           })
           yield* merge(source, next, "local")
-          log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
+          yield* Effect.logDebug("loaded custom config from OPENCODE_CONFIG_CONTENT")
         }
 
         const activeAccount = Option.getOrUndefined(
@@ -498,12 +492,11 @@ export const layer = Layer.effect(
             }
           }).pipe(
             Effect.withSpan("Config.loadActiveOrgConfig"),
-            Effect.catch((err) => {
-              log.debug("failed to fetch remote account config", {
+            Effect.catch((err) =>
+              Effect.logDebug("failed to fetch remote account config", {
                 error: err instanceof Error ? err.message : String(err),
-              })
-              return Effect.void
-            }),
+              }),
+            ),
           )
         }
 
@@ -540,7 +533,7 @@ export const layer = Layer.effect(
           try {
             result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION))
           } catch (err) {
-            log.warn("OPENCODE_PERMISSION contains invalid JSON, skipping", { err })
+            yield* Effect.logWarning("OPENCODE_PERMISSION contains invalid JSON, skipping", { err })
           }
         }
 
@@ -561,7 +554,7 @@ export const layer = Layer.effect(
           try {
             result.username = os.userInfo().username || "user"
           } catch (err) {
-            log.warn("failed to read system username, using fallback", { err })
+            yield* Effect.logWarning("failed to read system username, using fallback", { err })
             result.username = "user"
           }
         }

+ 2 - 10
packages/opencode/src/config/managed.ts

@@ -3,11 +3,8 @@ export * as ConfigManaged from "./managed"
 import { existsSync } from "fs"
 import os from "os"
 import path from "path"
-import * as Log from "@opencode-ai/core/util/log"
 import { Process } from "@/util/process"
 
-const log = Log.create({ service: "config" })
-
 const MANAGED_PLIST_DOMAIN = "ai.opencode.managed"
 
 // Keys injected by macOS/MDM into the managed plist that are not OpenCode config
@@ -49,8 +46,7 @@ export async function readManagedPreferences() {
   const user = (() => {
     try {
       return os.userInfo().username || "user"
-    } catch (err) {
-      log.warn("failed to read system username, using fallback", { err })
+    } catch {
       return "user"
     }
   })()
@@ -61,12 +57,8 @@ export async function readManagedPreferences() {
 
   for (const plist of paths) {
     if (!existsSync(plist)) continue
-    log.info("reading macOS managed preferences", { path: plist })
     const result = await Process.run(["plutil", "-convert", "json", "-o", "-", plist], { nothrow: true })
-    if (result.code !== 0) {
-      log.warn("failed to convert managed preferences plist", { path: plist })
-      continue
-    }
+    if (result.code !== 0) continue
     return {
       source: `mobileconfig:${plist}`,
       text: parseManagedPlist(result.stdout.toString()),

+ 6 - 28
packages/opencode/src/config/tui-migrate.ts

@@ -6,11 +6,8 @@ import { TuiConfig } from "@opencode-ai/tui/config"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { Global } from "@opencode-ai/core/global"
 import { Filesystem } from "@/util/filesystem"
-import * as Log from "@opencode-ai/core/util/log"
 import * as ConfigPaths from "@/config/paths"
 
-const log = Log.create({ service: "tui.migrate" })
-
 const TUI_SCHEMA_URL = "https://opencode.ai/tui.json"
 
 const decodeTheme = Schema.decodeUnknownOption(Schema.String)
@@ -32,10 +29,7 @@ interface MigrateInput {
 export async function migrateTuiConfig(input: MigrateInput) {
   const opencode = await opencodeFiles(input)
   for (const file of opencode) {
-    const source = await Filesystem.readText(file).catch((error) => {
-      log.warn("failed to read config for tui migration", { path: file, error })
-      return undefined
-    })
+    const source = await Filesystem.readText(file).catch(() => undefined)
     if (!source) continue
     const errors: JsoncParseError[] = []
     const data = parseJsonc(source, errors, { allowTrailingComma: true })
@@ -65,18 +59,11 @@ export async function migrateTuiConfig(input: MigrateInput) {
 
     const wrote = await Filesystem.write(target, JSON.stringify(payload, null, 2))
       .then(() => true)
-      .catch((error) => {
-        log.warn("failed to write tui migration target", { from: file, to: target, error })
-        return false
-      })
+      .catch(() => false)
     if (!wrote) continue
 
     const stripped = await backupAndStripLegacy(file, source)
-    if (!stripped) {
-      log.warn("tui config migrated but source file was not stripped", { from: file, to: target })
-      continue
-    }
-    log.info("migrated tui config", { from: file, to: target })
+    if (!stripped) continue
   }
 }
 
@@ -106,10 +93,7 @@ async function backupAndStripLegacy(file: string, source: string) {
     ? true
     : await Filesystem.write(backup, source)
         .then(() => true)
-        .catch((error) => {
-          log.warn("failed to backup source config during tui migration", { path: file, backup, error })
-          return false
-        })
+        .catch(() => false)
   if (!backed) return false
 
   const text = ["theme", "keybinds", "tui"].reduce((acc, key) => {
@@ -124,14 +108,8 @@ async function backupAndStripLegacy(file: string, source: string) {
   }, source)
 
   return Filesystem.write(file, text)
-    .then(() => {
-      log.info("stripped tui keys from server config", { path: file, backup })
-      return true
-    })
-    .catch((error) => {
-      log.warn("failed to strip legacy tui keys from server config", { path: file, backup, error })
-      return false
-    })
+    .then(() => true)
+    .catch(() => false)
 }
 
 async function opencodeFiles(input: { directories: string[]; cwd: string }) {

+ 13 - 31
packages/opencode/src/config/tui.ts

@@ -17,14 +17,11 @@ import { TuiKeybind } from "@opencode-ai/tui/config/keybind"
 import { InstallationLocal, InstallationVersion } from "@opencode-ai/core/installation/version"
 import { makeRuntime } from "@opencode-ai/core/effect/runtime"
 import { Filesystem } from "@/util/filesystem"
-import * as Log from "@opencode-ai/core/util/log"
 import { ConfigVariable } from "@/config/variable"
 import { Npm } from "@opencode-ai/core/npm"
 import { FormatError, FormatUnknownError } from "@/cli/error"
 import { TuiConfig } from "@opencode-ai/tui/config"
 
-const log = Log.create({ service: "tui.config" })
-
 export const Info = TuiConfig.Info
 export type Info = TuiConfig.Info
 
@@ -69,17 +66,12 @@ function normalize(raw: Record<string, unknown>) {
   }
 }
 
-function dropUnknownKeybinds(input: Record<string, unknown>, configFilepath: string) {
+function dropUnknownKeybinds(input: Record<string, unknown>) {
   if (!isRecord(input.keybinds)) return input
 
   const invalid = TuiKeybind.unknownKeys(input.keybinds)
   if (!invalid.length) return input
 
-  log.warn("ignored unknown tui keybinds", {
-    path: configFilepath,
-    keybinds: invalid,
-    hint: "Remove these entries or rename them to keys from the tui.json schema.",
-  })
   return {
     ...input,
     keybinds: Object.fromEntries(Object.entries(input.keybinds).filter(([key]) => !invalid.includes(key))),
@@ -111,7 +103,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
       if (!isRecord(data)) return {} as Info
       // Flatten a nested "tui" key so users who wrote `{ "tui": { ... } }` inside tui.json
       // (mirroring the old opencode.json shape) still get their settings applied.
-      const normalized = dropUnknownKeybinds(normalize(data), configFilepath)
+      const normalized = dropUnknownKeybinds(normalize(data))
       const parsed = ConfigParse.schema(Info, normalized, configFilepath)
       const validated = parsed.attention?.sounds
         ? {
@@ -127,15 +119,10 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
       // catchCause (not tapErrorCause + orElseSucceed) because JSONC parsing and validation
       // can sync-throw — those become defects, which orElseSucceed wouldn't catch.
       Effect.catchCause((cause) =>
-        Effect.sync(() => {
-          const error = Cause.squash(cause)
-          const reason = FormatError(error) ?? FormatUnknownError(error)
-          log.warn("skipping invalid tui config", {
-            path: configFilepath,
-            reason,
-          })
-          return {} as Info
-        }),
+        Effect.logWarning("skipping invalid tui config", {
+          path: configFilepath,
+          reason: FormatError(Cause.squash(cause)) ?? FormatUnknownError(Cause.squash(cause)),
+        }).pipe(Effect.as({} as Info)),
       ),
     )
 
@@ -146,19 +133,14 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
       // broken-config path degrades gracefully rather than crashing TUI startup.
       const text = yield* afs.readFileStringSafe(filepath).pipe(
         Effect.catchCause((cause) =>
-          Effect.sync(() => {
-            const error = Cause.squash(cause)
-            const reason = FormatError(error) ?? FormatUnknownError(error)
-            log.warn("failed to read tui config", {
-              path: filepath,
-              reason,
-            })
-            return undefined
-          }),
+          Effect.logWarning("failed to read tui config", {
+            path: filepath,
+            reason: FormatError(Cause.squash(cause)) ?? FormatUnknownError(Cause.squash(cause)),
+          }).pipe(Effect.as(undefined)),
         ),
       )
       if (!text) return {} as Info
-      log.info("loading tui config", { path: filepath })
+      yield* Effect.logInfo("loading tui config", { path: filepath })
       return yield* load(text, filepath)
     })
 
@@ -167,7 +149,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
       const data = yield* loadFile(file)
       if (Object.keys(data).length) {
         appliedOrder += 1
-        log.info("applying tui config", { path: file, order: appliedOrder })
+        yield* Effect.logInfo("applying tui config", { path: file, order: appliedOrder })
       }
       acc.result = mergeDeep(acc.result, data)
       if (!data.plugin?.length) return
@@ -205,7 +187,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
   if (Flag.OPENCODE_TUI_CONFIG) {
     const configFile = Flag.OPENCODE_TUI_CONFIG
     yield* mergeFile(acc, configFile)
-    log.debug("loaded custom tui config", { path: configFile })
+    yield* Effect.logDebug("loaded custom tui config", { path: configFile })
   }
 
   // 3. Project tui files, applied root-first so the closest file wins.

+ 24 - 116
packages/opencode/src/control-plane/workspace.ts

@@ -12,7 +12,6 @@ import { EventV2 } from "@opencode-ai/core/event"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { EventSequenceTable, EventTable } from "@opencode-ai/core/event/sql"
 import { FSUtil } from "@opencode-ai/core/fs-util"
-import * as Log from "@opencode-ai/core/util/log"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { ProjectV2 } from "@opencode-ai/core/project"
 import { Slug } from "@opencode-ai/core/util/slug"
@@ -74,7 +73,6 @@ function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
   }
 }
 
-const log = Log.create({ service: "workspace-sync" })
 
 export const CreateInput = Schema.Struct({
   id: Schema.optional(WorkspaceV2.ID),
@@ -292,18 +290,16 @@ export const layer = Layer.effect(
 
         const response = yield* http.execute(input.remote({ workspace, target })).pipe(
           Effect.catch((error) =>
-            Effect.sync(() => {
-              log.warn("workspace target request failed", {
-                workspaceID: workspace.id,
-                error: errorData(error),
-              })
-            }),
+            Effect.logWarning("workspace target request failed", {
+              workspaceID: workspace.id,
+              error: errorData(error),
+            }).pipe(Effect.as(undefined)),
           ),
         )
         if (!response) return input.fallback
         if (response.status < 200 || response.status >= 300) {
           const body = yield* response.text.pipe(Effect.catch(() => Effect.succeed("")))
-          log.warn("workspace target request failed", {
+          yield* Effect.logWarning("workspace target request failed", {
             workspaceID: workspace.id,
             status: response.status,
             body,
@@ -315,13 +311,10 @@ export const layer = Layer.effect(
         return yield* body.pipe(
           Effect.map((result) => result as A),
           Effect.catch((error) =>
-            Effect.sync(() => {
-              log.warn("workspace target response decode failed", {
-                workspaceID: workspace.id,
-                error: errorData(error),
-              })
-              return input.fallback
-            }),
+            Effect.logWarning("workspace target response decode failed", {
+              workspaceID: workspace.id,
+              error: errorData(error),
+            }).pipe(Effect.as(input.fallback)),
           ),
         )
       })
@@ -348,11 +341,6 @@ export const layer = Layer.effect(
           )
         : {}
 
-      log.info("syncing workspace history", {
-        workspaceID: space.id,
-        sessions: sessionIDs.length,
-        known: Object.keys(state).length,
-      })
 
       const response = yield* http.execute(
         HttpClientRequest.post(route(url, "/sync/history"), {
@@ -372,10 +360,6 @@ export const layer = Layer.effect(
 
       const history = (yield* response.json) as HistoryEvent[]
 
-      log.info("workspace history synced", {
-        workspaceID: space.id,
-        events: history.length,
-      })
 
       yield* Effect.forEach(
         history,
@@ -404,17 +388,16 @@ export const layer = Layer.effect(
       let attempt = 0
 
       while (true) {
-        log.info("connecting to global sync", { workspace: space.name })
         setStatus(space.id, "connecting")
 
         const stream = yield* connectSSE(target.url, target.headers).pipe(
           Effect.tap(() => syncHistory(space, target.url, target.headers)),
           Effect.catch((err) =>
-            Effect.sync(() => {
+            Effect.gen(function* () {
               setStatus(space.id, "error")
-              log.info("failed to connect to global sync", {
+              yield* Effect.logWarning("failed to connect to global sync", {
                 workspace: space.name,
-                err,
+                error: errorData(err),
               })
               return null
             }),
@@ -424,7 +407,6 @@ export const layer = Layer.effect(
         if (stream) {
           attempt = 0
 
-          log.info("global sync connected", { workspace: space.name })
           setStatus(space.id, "connected")
 
           yield* parseSSE(stream, (evt) =>
@@ -437,13 +419,10 @@ export const layer = Layer.effect(
                 const failed = yield* events.replay(payload.syncEvent, { publish: true, ownerID: space.id }).pipe(
                   Effect.as(false),
                   Effect.catchCause((error) =>
-                    Effect.sync(() => {
-                      log.info("failed to replay global event", {
-                        workspaceID: space.id,
-                        error,
-                      })
-                      return true
-                    }),
+                    Effect.logWarning("failed to replay global event", error).pipe(
+                      Effect.annotateLogs({ workspaceID: space.id }),
+                      Effect.as(true),
+                    ),
                   ),
                 )
                 if (failed) return
@@ -458,15 +437,14 @@ export const layer = Layer.effect(
                   payload: event.payload,
                 })
               } catch (error) {
-                log.info("failed to replay global event", {
+                yield* Effect.logWarning("failed to emit global event", {
                   workspaceID: space.id,
-                  error,
+                  error: errorData(error),
                 })
               }
             }),
           )
 
-          log.info("disconnected from global sync: " + space.id)
           setStatus(space.id, "disconnected")
         }
 
@@ -482,9 +460,9 @@ export const layer = Layer.effect(
 
       const target = yield* WorkspaceAdapterRuntime.target(space).pipe(
         Effect.catch((error) =>
-          Effect.sync(() => {
+          Effect.gen(function* () {
             setStatus(space.id, "error")
-            log.warn("workspace target failed", {
+            yield* Effect.logWarning("workspace target failed", {
               workspaceID: space.id,
               error: errorData(error),
             })
@@ -511,11 +489,11 @@ export const layer = Layer.effect(
         // allow the fiber to fail and automatically get removed
         syncWorkspaceLoop(space).pipe(
           Effect.catch((error) =>
-            Effect.sync(() => {
+            Effect.gen(function* () {
               setStatus(space.id, "error")
-              log.warn("workspace listener failed", {
+              yield* Effect.logWarning("workspace listener failed", {
                 workspaceID: space.id,
-                error,
+                error: errorData(error),
               })
             }),
           ),
@@ -597,10 +575,6 @@ export const layer = Layer.effect(
 
     const sessionWarp = Effect.fn("Workspace.sessionWarp")(function* (input: SessionWarpInput) {
       return yield* Effect.gen(function* () {
-        log.info("session warp requested", {
-          workspaceID: input.workspaceID,
-          sessionID: input.sessionID,
-        })
 
         const current = yield* db
           .select({ workspaceID: SessionTable.workspace_id })
@@ -618,11 +592,6 @@ export const layer = Layer.effect(
               yield* syncHistory(previous, target.url, target.headers).pipe(
                 Effect.catch((error) =>
                   Effect.sync(() => {
-                    log.warn("session warp final source sync failed", {
-                      workspaceID: previous.id,
-                      sessionID: input.sessionID,
-                      error: errorData(error),
-                    })
                   }),
                 ),
               )
@@ -669,11 +638,6 @@ export const layer = Layer.effect(
         if (input.workspaceID === null) {
           yield* session.setWorkspace({ sessionID: input.sessionID, workspaceID: undefined })
 
-          log.info("session warp complete", {
-            workspaceID: input.workspaceID,
-            sessionID: input.sessionID,
-            target: "local",
-          })
           return
         }
 
@@ -690,11 +654,6 @@ export const layer = Layer.effect(
         if (target.type === "local") {
           yield* session.setWorkspace({ sessionID: input.sessionID, workspaceID: input.workspaceID })
 
-          log.info("session warp complete", {
-            workspaceID: input.workspaceID,
-            sessionID: input.sessionID,
-            target: target.directory,
-          })
           return
         }
 
@@ -720,15 +679,6 @@ export const layer = Layer.effect(
         const batches = Iterable.chunksOf(rows, 10)
         const total = Iterable.size(batches)
 
-        log.info("session warp prepared", {
-          workspaceID: input.workspaceID,
-          sessionID: input.sessionID,
-          target: String(route(target.url, "/sync/replay")),
-          events: rows.length,
-          batches: total,
-          first: rows[0]?.seq,
-          last: rows.at(-1)?.seq,
-        })
 
         yield* Effect.forEach(
           batches,
@@ -746,14 +696,6 @@ export const layer = Layer.effect(
 
               if (response.status < 200 || response.status >= 300) {
                 const body = yield* response.text
-                log.error("session warp batch failed", {
-                  workspaceID: input.workspaceID,
-                  sessionID: input.sessionID,
-                  step: i + 1,
-                  total,
-                  status: response.status,
-                  body,
-                })
                 return yield* new SessionWarpHttpError({
                   message: `Failed to warp session ${input.sessionID} into workspace ${workspaceID}: HTTP ${response.status} ${body}`,
                   workspaceID,
@@ -763,13 +705,6 @@ export const layer = Layer.effect(
                 })
               }
 
-              log.info("session warp batch posted", {
-                workspaceID: input.workspaceID,
-                sessionID: input.sessionID,
-                step: i + 1,
-                total,
-                status: response.status,
-              })
             }),
           { discard: true },
         )
@@ -782,12 +717,6 @@ export const layer = Layer.effect(
         )
         if (response.status < 200 || response.status >= 300) {
           const body = yield* response.text
-          log.error("session warp steal failed", {
-            workspaceID: input.workspaceID,
-            sessionID: input.sessionID,
-            status: response.status,
-            body,
-          })
           return yield* new SessionWarpHttpError({
             message: `Failed to steal session ${input.sessionID} into workspace ${workspaceID}: HTTP ${response.status} ${body}`,
             workspaceID,
@@ -799,22 +728,7 @@ export const layer = Layer.effect(
 
         yield* session.setWorkspace({ sessionID: input.sessionID, workspaceID: input.workspaceID })
 
-        log.info("session warp complete", {
-          workspaceID: input.workspaceID,
-          sessionID: input.sessionID,
-          batches: total,
-        })
-      }).pipe(
-        Effect.tapError((err) =>
-          Effect.sync(() =>
-            log.error("session warp failed", {
-              workspaceID: input.workspaceID,
-              sessionID: input.sessionID,
-              error: errorData(err),
-            }),
-          ),
-        ),
-      )
+      })
     })
 
     const list = Effect.fn("Workspace.list")(function* (project: Project.Info) {
@@ -836,7 +750,6 @@ export const layer = Layer.effect(
           WorkspaceAdapterRuntime.list(adapter).pipe(
             Effect.catchCause((error) =>
               Effect.sync(() => {
-                log.warn("workspace adapter list failed", { type, error })
                 return []
               }),
             ),
@@ -916,7 +829,6 @@ export const layer = Layer.effect(
         }),
         () =>
           Effect.sync(() => {
-            log.error("adapter not available when removing workspace", { type: row.type })
           }),
       )
 
@@ -973,10 +885,6 @@ export const layer = Layer.effect(
           Effect.catch((error) =>
             Effect.sync(() => {
               setStatus(workspace.id, "error")
-              log.warn("workspace sync failed to start", {
-                workspaceID: workspace.id,
-                error,
-              })
             }),
           ),
           Effect.forkDetach,

+ 1 - 1
packages/opencode/src/effect/app-runtime.ts

@@ -1,6 +1,6 @@
 import { Layer, ManagedRuntime } from "effect"
 import { attach } from "./run-service"
-import * as Observability from "@opencode-ai/core/effect/observability"
+import * as Observability from "@opencode-ai/core/observability"
 
 import { FSUtil } from "@opencode-ai/core/fs-util"
 import { Database } from "@opencode-ai/core/database/database"

+ 1 - 1
packages/opencode/src/effect/bootstrap-runtime.ts

@@ -7,7 +7,7 @@ import { ShareNext } from "@/share/share-next"
 import { Vcs } from "@/project/vcs"
 import { Snapshot } from "@/snapshot"
 import { Config } from "@/config/config"
-import * as Observability from "@opencode-ai/core/effect/observability"
+import * as Observability from "@opencode-ai/core/observability"
 import { memoMap } from "@opencode-ai/core/effect/memo-map"
 
 export const BootstrapLayer = Layer.mergeAll(

+ 1 - 4
packages/opencode/src/effect/instance-state.ts

@@ -1,5 +1,4 @@
 import { Effect, ScopedCache, Scope } from "effect"
-import * as EffectLogger from "@opencode-ai/core/effect/logger"
 import type { InstanceContext } from "@/project/instance-context"
 import { InstanceRef, WorkspaceRef } from "./instance-ref"
 import { registerDisposer } from "./instance-registry"
@@ -36,9 +35,7 @@ export const make = <A, E = never, R = never>(
         }),
     })
 
-    const off = registerDisposer((directory) =>
-      Effect.runPromise(ScopedCache.invalidate(cache, directory).pipe(Effect.provide(EffectLogger.layer))),
-    )
+    const off = registerDisposer((directory) => Effect.runPromise(ScopedCache.invalidate(cache, directory)))
     yield* Effect.addFinalizer(() => Effect.sync(off))
 
     return {

+ 1 - 1
packages/opencode/src/effect/run-service.ts

@@ -1,7 +1,7 @@
 import { Effect, Fiber, Layer, ManagedRuntime } from "effect"
 import * as Context from "effect/Context"
 import { InstanceRef, WorkspaceRef } from "./instance-ref"
-import * as Observability from "@opencode-ai/core/effect/observability"
+import * as Observability from "@opencode-ai/core/observability"
 import { WorkspaceContext } from "@/control-plane/workspace-context"
 import type { InstanceContext } from "@/project/instance-context"
 import { memoMap } from "@opencode-ai/core/effect/memo-map"

+ 8 - 18
packages/opencode/src/format/index.ts

@@ -8,11 +8,8 @@ import { mergeDeep } from "remeda"
 import { Config } from "@/config/config"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { errorMessage } from "@/util/error"
-import * as Log from "@opencode-ai/core/util/log"
 import * as Formatter from "./formatter"
 
-const log = Log.create({ service: "format" })
-
 export const Status = Schema.Struct({
   name: Schema.String,
   extensions: Schema.Array(Schema.String),
@@ -60,11 +57,7 @@ export const layer = Layer.effect(
           const matching = Object.values(formatters).filter((item) => item.extensions.includes(ext))
           const checks = await Promise.all(
             matching.map(async (item) => {
-              log.info("checking", { name: item.name, ext })
               const cmd = await getCommand(item)
-              if (cmd) {
-                log.info("enabled", { name: item.name, ext })
-              }
               return {
                 item,
                 cmd,
@@ -78,13 +71,13 @@ export const layer = Layer.effect(
 
         function formatFile(filepath: string) {
           return Effect.gen(function* () {
-            log.info("formatting", { file: filepath })
+            yield* Effect.logInfo("formatting", { file: filepath })
             const formatters = yield* Effect.promise(() => getFormatter(path.extname(filepath)))
 
             if (!formatters.length) return false
 
             for (const { item, cmd } of formatters) {
-              log.info("running", { command: cmd })
+              yield* Effect.logInfo("running", { command: cmd })
               const replaced = cmd.map((x) => x.replace("$FILE", filepath))
               const dir = yield* InstanceState.directory
               const result = yield* appProcess
@@ -100,20 +93,17 @@ export const layer = Layer.effect(
                 )
                 .pipe(
                   Effect.catch((error) =>
-                    Effect.sync(() => {
-                      log.error("failed to format file", {
+                    Effect.logError("failed to format file", {
                         error: "spawn failed",
                         command: cmd,
                         ...item.environment,
                         file: filepath,
                         cause: errorMessage(error.cause ?? error),
-                      })
-                      return undefined
-                    }),
+                      }).pipe(Effect.as(undefined)),
                   ),
                 )
               if (result && result.exitCode !== 0) {
-                log.error("failed", {
+                yield* Effect.logError("failed", {
                   command: cmd,
                   ...item.environment,
                 })
@@ -127,8 +117,8 @@ export const layer = Layer.effect(
         const cfg = yield* config.get()
 
         if (!cfg.formatter) {
-          log.info("all formatters are disabled")
-          log.info("init")
+          yield* Effect.logInfo("all formatters are disabled")
+          yield* Effect.logInfo("init")
           return {
             formatters,
             isEnabled,
@@ -166,7 +156,7 @@ export const layer = Layer.effect(
           }
         }
 
-        log.info("init")
+        yield* Effect.logInfo("init")
 
         return {
           formatters,

+ 0 - 9
packages/opencode/src/ide/index.ts

@@ -1,7 +1,6 @@
 import { EventV2 } from "@opencode-ai/core/event"
 import { Schema } from "effect"
 import { NamedError } from "@opencode-ai/core/util/error"
-import * as Log from "@opencode-ai/core/util/log"
 import { Process } from "@/util/process"
 
 const SUPPORTED_IDES = [
@@ -12,8 +11,6 @@ const SUPPORTED_IDES = [
   { name: "VSCodium" as const, cmd: "codium" },
 ]
 
-const log = Log.create({ service: "ide" })
-
 export const Event = {
   Installed: EventV2.define({
     type: "ide.installed",
@@ -53,12 +50,6 @@ export async function install(ide: (typeof SUPPORTED_IDES)[number]["name"]) {
   const stdout = p.stdout.toString()
   const stderr = p.stderr.toString()
 
-  log.info("installed", {
-    ide,
-    stdout,
-    stderr,
-  })
-
   if (p.code !== 0) {
     throw new InstallFailedError({ stderr })
   }

+ 4 - 10
packages/opencode/src/image/image.ts

@@ -1,7 +1,6 @@
 import { Config } from "@/config/config"
 import { SessionV1 } from "@opencode-ai/core/v1/session"
 import type { MessageV2 } from "@/session/message-v2"
-import * as Log from "@opencode-ai/core/util/log"
 import photonWasm from "@silvia-odwyer/photon-node/photon_rs_bg.wasm" with { type: "file" }
 import { Context, Effect, Layer, Schema } from "effect"
 import path from "node:path"
@@ -12,8 +11,6 @@ const MAX_WIDTH = 2000
 const MAX_HEIGHT = 2000
 const AUTO_RESIZE = true
 const JPEG_QUALITIES = [80, 85, 70, 55, 40]
-const log = Log.create({ service: "image" })
-
 export class ResizerUnavailableError extends Schema.TaggedErrorClass<ResizerUnavailableError>()(
   "ImageResizerUnavailableError",
   {},
@@ -69,7 +66,7 @@ export const layer = Layer.effect(
           path.isAbsolute(photonWasm) ? photonWasm : fileURLToPath(new URL(photonWasm, import.meta.url))
       }).pipe(
         Effect.andThen(() => Effect.tryPromise(() => import("@silvia-odwyer/photon-node"))),
-        Effect.tapError((error) => Effect.sync(() => log.warn("failed to load photon", { error }))),
+        Effect.tapError((error) => Effect.logWarning("failed to load photon", { error })),
         Effect.mapError(() => new ResizerUnavailableError()),
       ),
     )
@@ -92,11 +89,8 @@ export const layer = Layer.effect(
 
       const decoded = yield* Effect.try({
         try: () => photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64")),
-        catch: (error) => {
-          log.warn("failed to decode image", { error })
-          return new DecodeError()
-        },
-      })
+        catch: () => new DecodeError(),
+      }).pipe(Effect.tapError((error) => Effect.logWarning("failed to decode image", { error })))
 
       try {
         const originalWidth = decoded.get_width()
@@ -141,7 +135,7 @@ export const layer = Layer.effect(
           resized.free()
 
           if (candidate) {
-            log.info("using resized image", {
+            yield* Effect.logInfo("using resized image", {
               from_mime: input.mime,
               to_mime: candidate.mime,
               from: `${originalWidth}x${originalHeight}`,

+ 3 - 68
packages/opencode/src/index.ts

@@ -2,7 +2,6 @@ import yargs from "yargs"
 import { hideBin } from "yargs/helpers"
 import { RunCommand } from "./cli/cmd/run"
 import { GenerateCommand } from "./cli/cmd/generate"
-import * as Log from "@opencode-ai/core/util/log"
 import { ConsoleCommand } from "./cli/cmd/account"
 import { ProvidersCommand } from "./cli/cmd/providers"
 import { AgentCommand } from "./cli/cmd/agent"
@@ -10,9 +9,7 @@ import { UpgradeCommand } from "./cli/cmd/upgrade"
 import { UninstallCommand } from "./cli/cmd/uninstall"
 import { ModelsCommand } from "./cli/cmd/models"
 import { UI } from "./cli/ui"
-import { Installation } from "./installation"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
-import { NamedError } from "@opencode-ai/core/util/error"
 import { FormatError } from "./cli/error"
 import { ServeCommand } from "./cli/cmd/serve"
 import { DebugCommand } from "./cli/cmd/debug"
@@ -32,22 +29,6 @@ import { DbCommand } from "./cli/cmd/db"
 import { errorMessage } from "./util/error"
 import { PluginCommand } from "./cli/cmd/plug"
 import { Heap } from "./cli/heap"
-import { ensureProcessMetadata } from "@opencode-ai/core/util/opencode-process"
-import { isRecord } from "@/util/record"
-
-const processMetadata = ensureProcessMetadata("main")
-
-process.on("unhandledRejection", (e) => {
-  Log.Default.error("rejection", {
-    e: errorMessage(e),
-  })
-})
-
-process.on("uncaughtException", (e) => {
-  Log.Default.error("exception", {
-    e: errorMessage(e),
-  })
-})
 
 const args = hideBin(process.argv)
 
@@ -83,32 +64,18 @@ const cli = yargs(args)
     type: "boolean",
   })
   .middleware(async (opts) => {
+    if (opts.printLogs) process.env.OPENCODE_PRINT_LOGS = "1"
+    if (opts.logLevel) process.env.OPENCODE_LOG_LEVEL = opts.logLevel
     if (opts.pure) {
       process.env.OPENCODE_PURE = "1"
     }
 
-    await Log.init({
-      print: process.argv.includes("--print-logs"),
-      dev: Installation.isLocal(),
-      level: (() => {
-        if (opts.logLevel) return opts.logLevel as Log.Level
-        if (Installation.isLocal()) return "DEBUG"
-        return "INFO"
-      })(),
-    })
-
     Heap.start()
 
     process.env.AGENT = "1"
     process.env.OPENCODE = "1"
     process.env.OPENCODE_PID = String(process.pid)
 
-    Log.Default.info("opencode", {
-      version: InstallationVersion,
-      args: process.argv.slice(2),
-      process_role: processMetadata.processRole,
-      run_id: processMetadata.runID,
-    })
   })
   .usage("")
   .completion("completion", "generate shell completion script")
@@ -160,42 +127,10 @@ try {
     await cli.parse()
   }
 } catch (e) {
-  let data: Record<string, any> = {}
-  if (e instanceof Error) {
-    Object.assign(data, {
-      name: e.name,
-      message: e.message,
-      cause: e.cause?.toString(),
-      stack: e.stack,
-    })
-  }
-
-  if (e instanceof NamedError) {
-    const obj = e.toObject()
-    if (isRecord(obj.data)) {
-      for (const [key, value] of Object.entries(obj.data)) {
-        if (key === "name" || key === "stack" || key === "cause") continue
-        data[key] = value
-      }
-    }
-  }
-
-  if (e instanceof ResolveMessage) {
-    Object.assign(data, {
-      name: e.name,
-      message: e.message,
-      code: e.code,
-      specifier: e.specifier,
-      referrer: e.referrer,
-      position: e.position,
-      importKind: e.importKind,
-    })
-  }
-  Log.Default.error("fatal", data)
   const formatted = FormatError(e)
   if (formatted) UI.error(formatted)
   if (formatted === undefined) {
-    UI.error("Unexpected error, check log file at " + Log.file() + " for more details" + EOL)
+    UI.error("Unexpected error" + EOL)
     process.stderr.write(errorMessage(e) + EOL)
   }
   process.exitCode = 1

+ 1 - 4
packages/opencode/src/installation/index.ts

@@ -7,14 +7,11 @@ import { ChildProcess } from "effect/unstable/process"
 import { AppProcess } from "@opencode-ai/core/process"
 import path from "path"
 import { EventV2 } from "@opencode-ai/core/event"
-import * as Log from "@opencode-ai/core/util/log"
 import { makeRuntime } from "@opencode-ai/core/effect/runtime"
 import semver from "semver"
 import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
 import { NpmConfig } from "@opencode-ai/core/npm-config"
 
-const log = Log.create({ service: "installation" })
-
 export type Method = "curl" | "npm" | "yarn" | "pnpm" | "bun" | "brew" | "scoop" | "choco" | "unknown"
 
 export type ReleaseType = "patch" | "minor" | "major"
@@ -324,7 +321,7 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
         if (!upgradeResult || upgradeResult.code !== 0) {
           return yield* new UpgradeFailedError({ stderr: upgradeFailure(m, upgradeResult) })
         }
-        log.info("upgraded", {
+        yield* Effect.logInfo("upgraded", {
           method: m,
           target,
           stdout: upgradeResult.stdout,

+ 1 - 37
packages/opencode/src/lsp/client.ts

@@ -2,7 +2,6 @@ import path from "path"
 import { pathToFileURL, fileURLToPath } from "url"
 import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
 import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
-import * as Log from "@opencode-ai/core/util/log"
 import { Process } from "@/util/process"
 import { LANGUAGE_EXTENSIONS } from "./language"
 import { Effect, Schema } from "effect"
@@ -23,7 +22,6 @@ const FILE_CHANGE_CREATED = 1
 const FILE_CHANGE_CHANGED = 2
 const TEXT_DOCUMENT_SYNC_INCREMENTAL = 2
 
-const log = Log.create({ service: "lsp.client" })
 export type Info = NonNullable<Awaited<ReturnType<typeof create>>>
 
 export type Diagnostic = VSCodeDiagnostic
@@ -129,23 +127,13 @@ export async function create(input: {
   directory: string
   instance: InstanceContext
 }) {
-  const logger = log.clone().tag("serverID", input.serverID)
-  logger.info("starting client")
   const instance = input.instance
 
   const connection = createMessageConnection(
     new StreamMessageReader(input.server.process.stdout as any),
     new StreamMessageWriter(input.server.process.stdin as any),
   )
-  // Server stderr can contain both real errors and routine informational logs,
-  // which is normal stderr practice for some tools. Keep the raw stream at
-  // debug so users can opt in with --print-logs --log-level DEBUG without
-  // polluting normal logs.
-  input.server.process.stderr?.on("data", (data: Buffer) => {
-    const text = data.toString().trim()
-    if (text) logger.debug("server stderr", { text: text.slice(0, 1000) })
-  })
-
+  input.server.process.stderr?.resume()
   // --- Connection state ---
 
   const pushDiagnostics = new Map<string, Diagnostic[]>()
@@ -172,11 +160,6 @@ export async function create(input: {
   connection.onNotification("textDocument/publishDiagnostics", (params) => {
     const filePath = getFilePath(params.uri)
     if (!filePath) return
-    logger.info("textDocument/publishDiagnostics", {
-      path: filePath,
-      count: params.diagnostics.length,
-      version: params.version,
-    })
     published.set(filePath, {
       at: Date.now(),
       version: typeof params.version === "number" ? params.version : undefined,
@@ -188,7 +171,6 @@ export async function create(input: {
     updatePushDiagnostics(filePath, params.diagnostics)
   })
   connection.onRequest("window/workDoneProgress/create", (params) => {
-    logger.info("window/workDoneProgress/create", params)
     return null
   })
   connection.onRequest("workspace/configuration", async (params) => {
@@ -226,7 +208,6 @@ export async function create(input: {
 
   // --- Initialize handshake ---
 
-  logger.info("sending initialize")
   const initialized = await withTimeout(
     connection.sendRequest<{ capabilities?: ServerCapabilities }>("initialize", {
       rootUri: pathToFileURL(input.root).href,
@@ -270,7 +251,6 @@ export async function create(input: {
     }),
     INITIALIZE_TIMEOUT_MS,
   ).catch((err) => {
-    logger.error("initialize error", { error: err })
     throw new InitializeError({ serverID: input.serverID, cause: err })
   })
 
@@ -585,7 +565,6 @@ export async function create(input: {
           // re-emit diagnostics when the content actually changes, so clearing
           // here would lose errors for no-op touchFile calls. Let the server's
           // next push/pull overwrite naturally.
-          logger.info("workspace/didChangeWatchedFiles", request)
           await connection.sendNotification("workspace/didChangeWatchedFiles", {
             changes: [
               {
@@ -597,10 +576,6 @@ export async function create(input: {
 
           const next = document.version + 1
           files[request.path] = { version: next, text }
-          logger.info("textDocument/didChange", {
-            path: request.path,
-            version: next,
-          })
           await connection.sendNotification("textDocument/didChange", {
             textDocument: {
               uri: pathToFileURL(request.path).href,
@@ -622,7 +597,6 @@ export async function create(input: {
           return next
         }
 
-        logger.info("workspace/didChangeWatchedFiles", request)
         await connection.sendNotification("workspace/didChangeWatchedFiles", {
           changes: [
             {
@@ -632,7 +606,6 @@ export async function create(input: {
           ],
         })
 
-        logger.info("textDocument/didOpen", request)
         pushDiagnostics.delete(request.path)
         pullDiagnostics.delete(request.path)
         await connection.sendNotification("textDocument/didOpen", {
@@ -658,11 +631,6 @@ export async function create(input: {
       const normalizedPath = Filesystem.normalizePath(
         path.isAbsolute(request.path) ? request.path : path.resolve(input.directory, request.path),
       )
-      logger.info("waiting for diagnostics", {
-        path: normalizedPath,
-        mode: request.mode ?? "full",
-        version: request.version,
-      })
       if (request.mode === "document") {
         await waitForDocumentDiagnostics({ path: normalizedPath, version: request.version, after: request.after })
         return
@@ -670,16 +638,12 @@ export async function create(input: {
       await waitForFullDiagnostics({ path: normalizedPath, version: request.version, after: request.after })
     },
     async shutdown() {
-      logger.info("shutting down")
       connection.end()
       connection.dispose()
       await Process.stop(input.server.process)
-      logger.info("shutdown")
     },
   }
 
-  logger.info("initialized")
-
   return result
 }
 

+ 7 - 17
packages/opencode/src/lsp/lsp.ts

@@ -1,6 +1,5 @@
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { EventV2 } from "@opencode-ai/core/event"
-import * as Log from "@opencode-ai/core/util/log"
 import * as LSPClient from "./client"
 import path from "path"
 import { pathToFileURL, fileURLToPath } from "url"
@@ -14,8 +13,6 @@ import { containsPath } from "@/project/instance-context"
 import { NonNegativeInt } from "@opencode-ai/core/schema"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 
-const log = Log.create({ service: "lsp" })
-
 export const Event = {
   Updated: EventV2.define({ type: "lsp.updated", schema: {} }),
 }
@@ -101,7 +98,6 @@ const kinds = [
 const filterExperimentalServers = (servers: Record<string, LSPServer.Info>, flags: RuntimeFlags.Info) => {
   if (flags.experimentalLspTy) {
     if (servers["pyright"]) {
-      log.info("LSP server pyright is disabled because OPENCODE_EXPERIMENTAL_LSP_TY is enabled")
       delete servers["pyright"]
     }
   } else {
@@ -153,7 +149,7 @@ export const layer = Layer.effect(
         const servers: Record<string, LSPServer.Info> = {}
 
         if (!cfg.lsp) {
-          log.info("all LSPs are disabled")
+          yield* Effect.logInfo("all LSPs are disabled")
         } else {
           for (const server of Object.values(LSPServer)) {
             servers[server.id] = server
@@ -165,7 +161,7 @@ export const layer = Layer.effect(
             for (const [name, item] of Object.entries(cfg.lsp)) {
               const existing = servers[name]
               if (item.disabled) {
-                log.info(`LSP server ${name} is disabled`)
+                yield* Effect.logInfo(`LSP server ${name} is disabled`)
                 delete servers[name]
                 continue
               }
@@ -185,7 +181,7 @@ export const layer = Layer.effect(
             }
           }
 
-          log.info("enabled LSP servers", {
+          yield* Effect.logInfo("enabled LSP servers", {
             serverIds: Object.values(servers)
               .map((server) => server.id)
               .join(", "),
@@ -225,25 +221,21 @@ export const layer = Layer.effect(
               if (!value) s.broken.add(key)
               return value
             })
-            .catch((err) => {
+            .catch(() => {
               s.broken.add(key)
-              log.error(`Failed to spawn LSP server ${server.id}`, { error: err })
               return undefined
             })
 
           if (!handle) return undefined
-          log.info("spawned lsp server", { serverID: server.id, root })
-
           const client = await LSPClient.create({
             serverID: server.id,
             server: handle,
             root,
             directory: ctx.directory,
             instance: ctx,
-          }).catch(async (err) => {
+          }).catch(async () => {
             s.broken.add(key)
             await Process.stop(handle.process)
-            log.error(`Failed to initialize LSP client ${server.id}`, { error: err })
             return undefined
           })
 
@@ -350,7 +342,7 @@ export const layer = Layer.effect(
     })
 
     const touchFile = Effect.fn("LSP.touchFile")(function* (input: string, diagnostics?: "document" | "full") {
-      log.info("touching file", { file: input })
+      yield* Effect.logInfo("touching file", { file: input })
       const clients = yield* getClients(input)
       yield* Effect.promise(() =>
         Promise.all(
@@ -365,9 +357,7 @@ export const layer = Layer.effect(
               after,
             })
           }),
-        ).catch((err) => {
-          log.error("failed to touch file", { err, file: input })
-        }),
+        ).catch(() => {}),
       )
     })
 

+ 0 - 124
packages/opencode/src/lsp/server.ts

@@ -2,7 +2,6 @@ import type { ChildProcessWithoutNullStreams } from "child_process"
 import path from "path"
 import os from "os"
 import { Global } from "@opencode-ai/core/global"
-import * as Log from "@opencode-ai/core/util/log"
 import { text } from "node:stream/consumers"
 import fs from "fs/promises"
 import { Filesystem } from "@/util/filesystem"
@@ -15,7 +14,6 @@ import { spawn } from "./launch"
 import { Npm } from "@opencode-ai/core/npm"
 import type { RuntimeFlags } from "@/effect/runtime-flags"
 
-const log = Log.create({ service: "lsp.server" })
 const pathExists = async (p: string) =>
   fs
     .stat(p)
@@ -104,7 +102,6 @@ export const Deno: Info = {
   async spawn(root) {
     const deno = which("deno")
     if (!deno) {
-      log.info("deno not found, please install deno first")
       return
     }
     return {
@@ -124,7 +121,6 @@ export const Typescript: Info = {
   extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
   async spawn(root, ctx) {
     const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory)
-    log.info("typescript server", { tsserver })
     if (!tsserver) return
     const bin = await Npm.which("typescript-language-server")
     if (!bin) return
@@ -181,11 +177,9 @@ export const ESLint: Info = {
   async spawn(root, ctx, flags) {
     const eslint = Module.resolve("eslint", ctx.directory)
     if (!eslint) return
-    log.info("spawning eslint server")
     const serverPath = path.join(Global.Path.bin, "vscode-eslint", "server", "out", "eslintServer.js")
     if (!(await Filesystem.exists(serverPath))) {
       if (flags.disableLspDownload) return
-      log.info("downloading and building VS Code ESLint server")
       const response = await fetch("https://github.com/microsoft/vscode-eslint/archive/refs/heads/main.zip")
       if (!response.ok) return
 
@@ -195,7 +189,6 @@ export const ESLint: Info = {
       const ok = await Archive.extractZip(zipPath, Global.Path.bin)
         .then(() => true)
         .catch((error) => {
-          log.error("Failed to extract vscode-eslint archive", { error })
           return false
         })
       if (!ok) return
@@ -206,7 +199,6 @@ export const ESLint: Info = {
 
       const stats = await fs.stat(finalPath).catch(() => undefined)
       if (stats) {
-        log.info("removing old eslint installation", { path: finalPath })
         await fs.rm(finalPath, { force: true, recursive: true })
       }
       await fs.rename(extractedPath, finalPath)
@@ -215,7 +207,6 @@ export const ESLint: Info = {
       await Process.run([npmCmd, "install"], { cwd: finalPath })
       await Process.run([npmCmd, "run", "compile"], { cwd: finalPath })
 
-      log.info("installed VS Code ESLint server", { serverPath })
     }
 
     const proc = spawn("node", [serverPath, "--stdio"], {
@@ -299,7 +290,6 @@ export const Oxlint: Info = {
       }
     }
 
-    log.info("oxlint not found, please install oxlint")
     return
   },
 }
@@ -380,7 +370,6 @@ export const Gopls: Info = {
       if (!which("go")) return
       if (flags.disableLspDownload) return
 
-      log.info("installing gopls")
       const proc = Process.spawn(["go", "install", "golang.org/x/tools/gopls@latest"], {
         env: { ...process.env, GOBIN: Global.Path.bin },
         stdout: "pipe",
@@ -389,13 +378,9 @@ export const Gopls: Info = {
       })
       const exit = await proc.exited
       if (exit !== 0) {
-        log.error("Failed to install gopls")
         return
       }
       bin = path.join(Global.Path.bin, "gopls" + (process.platform === "win32" ? ".exe" : ""))
-      log.info(`installed gopls`, {
-        bin,
-      })
     }
     return {
       process: spawn(bin!, {
@@ -415,11 +400,9 @@ export const Rubocop: Info = {
       const ruby = which("ruby")
       const gem = which("gem")
       if (!ruby || !gem) {
-        log.info("Ruby not found, please install Ruby first")
         return
       }
       if (flags.disableLspDownload) return
-      log.info("installing rubocop")
       const proc = Process.spawn(["gem", "install", "rubocop", "--bindir", Global.Path.bin], {
         stdout: "pipe",
         stderr: "pipe",
@@ -427,13 +410,9 @@ export const Rubocop: Info = {
       })
       const exit = await proc.exited
       if (exit !== 0) {
-        log.error("Failed to install rubocop")
         return
       }
       bin = path.join(Global.Path.bin, "rubocop" + (process.platform === "win32" ? ".exe" : ""))
-      log.info(`installed rubocop`, {
-        bin,
-      })
     }
     return {
       process: spawn(bin!, ["--lsp"], {
@@ -490,7 +469,6 @@ export const Ty: Info = {
     }
 
     if (!binary) {
-      log.error("ty not found, please install ty first")
       return
     }
 
@@ -567,12 +545,10 @@ export const ElixirLS: Info = {
       if (!(await Filesystem.exists(binary))) {
         const elixir = which("elixir")
         if (!elixir) {
-          log.error("elixir is required to run elixir-ls")
           return
         }
 
         if (flags.disableLspDownload) return
-        log.info("downloading elixir-ls from GitHub releases")
 
         const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
         if (!response.ok) return
@@ -582,7 +558,6 @@ export const ElixirLS: Info = {
         const ok = await Archive.extractZip(zipPath, Global.Path.bin)
           .then(() => true)
           .catch((error) => {
-            log.error("Failed to extract elixir-ls archive", { error })
             return false
           })
         if (!ok) return
@@ -598,9 +573,6 @@ export const ElixirLS: Info = {
         await Process.run(["mix", "compile"], { cwd, env })
         await Process.run(["mix", "elixir_ls.release2", "-o", "release"], { cwd, env })
 
-        log.info(`installed elixir-ls`, {
-          path: elixirLsPath,
-        })
       }
     }
 
@@ -622,16 +594,13 @@ export const Zls: Info = {
     if (!bin) {
       const zig = which("zig")
       if (!zig) {
-        log.error("Zig is required to use zls. Please install Zig first.")
         return
       }
 
       if (flags.disableLspDownload) return
-      log.info("downloading zls from GitHub releases")
 
       const releaseResponse = await fetch("https://api.github.com/repos/zigtools/zls/releases/latest")
       if (!releaseResponse.ok) {
-        log.error("Failed to fetch zls release info")
         return
       }
 
@@ -668,20 +637,17 @@ export const Zls: Info = {
       ]
 
       if (!supportedCombos.includes(assetName)) {
-        log.error(`Platform ${platform} and architecture ${arch} is not supported by zls`)
         return
       }
 
       const asset = release.assets?.find((a) => a.name === assetName)
       if (!asset?.browser_download_url) {
-        log.error(`Could not find asset ${assetName} in latest zls release`)
         return
       }
 
       const downloadUrl = asset.browser_download_url
       const downloadResponse = await fetch(downloadUrl)
       if (!downloadResponse.ok) {
-        log.error("Failed to download zls")
         return
       }
 
@@ -692,7 +658,6 @@ export const Zls: Info = {
         const ok = await Archive.extractZip(tempPath, Global.Path.bin)
           .then(() => true)
           .catch((error) => {
-            log.error("Failed to extract zls archive", { error })
             return false
           })
         if (!ok) return
@@ -705,7 +670,6 @@ export const Zls: Info = {
       bin = path.join(Global.Path.bin, "zls" + (platform === "win32" ? ".exe" : ""))
 
       if (!(await Filesystem.exists(bin))) {
-        log.error("Failed to extract zls binary")
         return
       }
 
@@ -713,7 +677,6 @@ export const Zls: Info = {
         await fs.chmod(bin, 0o755).catch(() => {})
       }
 
-      log.info(`installed zls`, { bin })
     }
 
     return {
@@ -750,11 +713,9 @@ export const Razor: Info = {
 
     const razor = await findVscodeRazorExtension()
     if (!razor) {
-      log.info("VS Code C# extension with Razor support not found, skipping Razor LSP")
       return
     }
 
-    log.info("using VS Code Razor extension for roslyn-language-server", { extension: razor.extension })
     return {
       process: spawn(
         bin,
@@ -791,12 +752,10 @@ async function getRoslynLanguageServer(disableLspDownload: boolean) {
 
 async function installRoslynLanguageServer(disableLspDownload: boolean) {
   if (!which("dotnet")) {
-    log.error(".NET SDK is required to install roslyn-language-server")
     return
   }
 
   if (disableLspDownload) return
-  log.info("installing roslyn-language-server via dotnet tool")
   const proc = Process.spawn(["dotnet", "tool", "install", "--global", "roslyn-language-server", "--prerelease"], {
     stdout: "pipe",
     stderr: "pipe",
@@ -804,23 +763,19 @@ async function installRoslynLanguageServer(disableLspDownload: boolean) {
   })
   const exit = await proc.exited
   if (exit !== 0) {
-    log.error("Failed to install roslyn-language-server")
     return
   }
 
   const resolved = which("roslyn-language-server")
   if (resolved) {
-    log.info(`installed roslyn-language-server`, { bin: resolved })
     return resolved
   }
 
   const global = await roslynLanguageServerGlobalPath()
   if (global) {
-    log.info(`installed roslyn-language-server`, { bin: global })
     return global
   }
 
-  log.error("Installed roslyn-language-server but could not resolve executable")
 }
 
 async function roslynLanguageServerGlobalPath() {
@@ -877,12 +832,10 @@ export const FSharp: Info = {
     let bin = which("fsautocomplete")
     if (!bin) {
       if (!which("dotnet")) {
-        log.error(".NET SDK is required to install fsautocomplete")
         return
       }
 
       if (flags.disableLspDownload) return
-      log.info("installing fsautocomplete via dotnet tool")
       const proc = Process.spawn(["dotnet", "tool", "install", "fsautocomplete", "--tool-path", Global.Path.bin], {
         stdout: "pipe",
         stderr: "pipe",
@@ -890,12 +843,10 @@ export const FSharp: Info = {
       })
       const exit = await proc.exited
       if (exit !== 0) {
-        log.error("Failed to install fsautocomplete")
         return
       }
 
       bin = path.join(Global.Path.bin, "fsautocomplete" + (process.platform === "win32" ? ".exe" : ""))
-      log.info(`installed fsautocomplete`, { bin })
     }
 
     return {
@@ -975,7 +926,6 @@ export const RustAnalyzer: Info = {
   async spawn(root) {
     const bin = which("rust-analyzer")
     if (!bin) {
-      log.info("rust-analyzer not found in path, please install it")
       return
     }
     return {
@@ -1026,11 +976,9 @@ export const Clangd: Info = {
     }
 
     if (flags.disableLspDownload) return
-    log.info("downloading clangd from GitHub releases")
 
     const releaseResponse = await fetch("https://api.github.com/repos/clangd/clangd/releases/latest")
     if (!releaseResponse.ok) {
-      log.error("Failed to fetch clangd release info")
       return
     }
 
@@ -1041,7 +989,6 @@ export const Clangd: Info = {
 
     const tag = release.tag_name
     if (!tag) {
-      log.error("clangd release did not include a tag name")
       return
     }
     const platform = process.platform
@@ -1052,7 +999,6 @@ export const Clangd: Info = {
     }
     const token = tokens[platform]
     if (!token) {
-      log.error(`Platform ${platform} is not supported by clangd auto-download`)
       return
     }
 
@@ -1069,21 +1015,18 @@ export const Clangd: Info = {
       assets.find((item) => valid(item) && item.name?.endsWith(".tar.xz")) ??
       assets.find((item) => valid(item))
     if (!asset?.name || !asset.browser_download_url) {
-      log.error("clangd could not match release asset", { tag, platform })
       return
     }
 
     const name = asset.name
     const downloadResponse = await fetch(asset.browser_download_url)
     if (!downloadResponse.ok) {
-      log.error("Failed to download clangd")
       return
     }
 
     const archive = path.join(Global.Path.bin, name)
     const buf = await downloadResponse.arrayBuffer()
     if (buf.byteLength === 0) {
-      log.error("Failed to write clangd archive")
       return
     }
     await Filesystem.write(archive, Buffer.from(buf))
@@ -1091,7 +1034,6 @@ export const Clangd: Info = {
     const zip = name.endsWith(".zip")
     const tar = name.endsWith(".tar.xz")
     if (!zip && !tar) {
-      log.error("clangd encountered unsupported asset", { asset: name })
       return
     }
 
@@ -1099,7 +1041,6 @@ export const Clangd: Info = {
       const ok = await Archive.extractZip(archive, Global.Path.bin)
         .then(() => true)
         .catch((error) => {
-          log.error("Failed to extract clangd archive", { error })
           return false
         })
       if (!ok) return
@@ -1111,7 +1052,6 @@ export const Clangd: Info = {
 
     const bin = path.join(Global.Path.bin, "clangd_" + tag, "bin", "clangd" + ext)
     if (!(await Filesystem.exists(bin))) {
-      log.error("Failed to extract clangd binary")
       return
     }
 
@@ -1122,7 +1062,6 @@ export const Clangd: Info = {
     await fs.unlink(path.join(Global.Path.bin, "clangd")).catch(() => {})
     await fs.symlink(bin, path.join(Global.Path.bin, "clangd")).catch(() => {})
 
-    log.info(`installed clangd`, { bin })
 
     return {
       process: spawn(bin, args, {
@@ -1166,7 +1105,6 @@ export const Astro: Info = {
   async spawn(root, ctx, flags) {
     const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory)
     if (!tsserver) {
-      log.info("typescript not found, required for Astro language server")
       return
     }
     const tsdk = path.dirname(tsserver)
@@ -1255,7 +1193,6 @@ export const JDTLS: Info = {
   async spawn(root, _ctx, flags) {
     const java = which("java")
     if (!java) {
-      log.error("Java 21 or newer is required to run the JDTLS. Please install it first.")
       return
     }
     const javaMajorVersion = await run(["java", "-version"]).then((result) => {
@@ -1263,7 +1200,6 @@ export const JDTLS: Info = {
       return !m ? undefined : parseInt(m[1])
     })
     if (javaMajorVersion == null || javaMajorVersion < 21) {
-      log.error("JDTLS requires at least Java 21.")
       return
     }
     const distPath = path.join(Global.Path.bin, "jdtls")
@@ -1271,29 +1207,23 @@ export const JDTLS: Info = {
     const installed = await pathExists(launcherDir)
     if (!installed) {
       if (flags.disableLspDownload) return
-      log.info("Downloading JDTLS LSP server.")
       await fs.mkdir(distPath, { recursive: true })
       const releaseURL =
         "https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz"
       const archiveName = "release.tar.gz"
 
-      log.info("Downloading JDTLS archive", { url: releaseURL, dest: distPath })
       const download = await fetch(releaseURL)
       if (!download.ok || !download.body) {
-        log.error("Failed to download JDTLS", { status: download.status, statusText: download.statusText })
         return
       }
       await Filesystem.writeStream(path.join(distPath, archiveName), download.body)
 
-      log.info("Extracting JDTLS archive")
       const tarResult = await run(["tar", "-xzf", archiveName], { cwd: distPath })
       if (tarResult.code !== 0) {
-        log.error("Failed to extract JDTLS", { exitCode: tarResult.code, stderr: tarResult.stderr.toString() })
         return
       }
 
       await fs.rm(path.join(distPath, archiveName), { force: true })
-      log.info("JDTLS download and extraction completed")
     }
     const jarFileName =
       (await fs.readdir(launcherDir).catch(() => []))
@@ -1301,7 +1231,6 @@ export const JDTLS: Info = {
         ?.trim() ?? ""
     const launcherJar = path.join(launcherDir, jarFileName)
     if (!(await pathExists(launcherJar))) {
-      log.error(`Failed to locate the JDTLS launcher module in the installed directory: ${distPath}.`)
       return
     }
     const configFile = path.join(
@@ -1369,11 +1298,9 @@ export const KotlinLS: Info = {
     const installed = await Filesystem.exists(launcherScript)
     if (!installed) {
       if (flags.disableLspDownload) return
-      log.info("Downloading Kotlin Language Server from GitHub.")
 
       const releaseResponse = await fetch("https://api.github.com/repos/Kotlin/kotlin-lsp/releases/latest")
       if (!releaseResponse.ok) {
-        log.error("Failed to fetch kotlin-lsp release info")
         return
       }
 
@@ -1381,7 +1308,6 @@ export const KotlinLS: Info = {
       const version = release.name?.replace(/^v/, "")
 
       if (!version) {
-        log.error("Could not determine Kotlin LSP version from release")
         return
       }
 
@@ -1402,7 +1328,6 @@ export const KotlinLS: Info = {
       const combo = `${kotlinPlatform}-${kotlinArch}`
 
       if (!supportedCombos.includes(combo)) {
-        log.error(`Platform ${platform}/${arch} is not supported by Kotlin LSP`)
         return
       }
 
@@ -1413,17 +1338,12 @@ export const KotlinLS: Info = {
       const archivePath = path.join(distPath, "kotlin-ls.zip")
       const download = await fetch(releaseURL)
       if (!download.ok || !download.body) {
-        log.error("Failed to download Kotlin Language Server", {
-          status: download.status,
-          statusText: download.statusText,
-        })
         return
       }
       await Filesystem.writeStream(archivePath, download.body)
       const ok = await Archive.extractZip(archivePath, distPath)
         .then(() => true)
         .catch((error) => {
-          log.error("Failed to extract Kotlin LS archive", { error })
           return false
         })
       if (!ok) return
@@ -1431,10 +1351,8 @@ export const KotlinLS: Info = {
       if (process.platform !== "win32") {
         await fs.chmod(launcherScript, 0o755).catch(() => {})
       }
-      log.info("Installed Kotlin Language Server", { path: launcherScript })
     }
     if (!(await Filesystem.exists(launcherScript))) {
-      log.error(`Failed to locate the Kotlin LS launcher script in the installed directory: ${distPath}.`)
       return
     }
     return {
@@ -1488,11 +1406,9 @@ export const LuaLS: Info = {
 
     if (!bin) {
       if (flags.disableLspDownload) return
-      log.info("downloading lua-language-server from GitHub releases")
 
       const releaseResponse = await fetch("https://api.github.com/repos/LuaLS/lua-language-server/releases/latest")
       if (!releaseResponse.ok) {
-        log.error("Failed to fetch lua-language-server release info")
         return
       }
 
@@ -1527,20 +1443,17 @@ export const LuaLS: Info = {
 
       const assetSuffix = `${lualsPlatform}-${lualsArch}.${ext}`
       if (!supportedCombos.includes(assetSuffix)) {
-        log.error(`Platform ${platform} and architecture ${arch} is not supported by lua-language-server`)
         return
       }
 
       const asset = release.assets.find((a: any) => a.name === assetName)
       if (!asset) {
-        log.error(`Could not find asset ${assetName} in latest lua-language-server release`)
         return
       }
 
       const downloadUrl = asset.browser_download_url
       const downloadResponse = await fetch(downloadUrl)
       if (!downloadResponse.ok) {
-        log.error("Failed to download lua-language-server")
         return
       }
 
@@ -1564,7 +1477,6 @@ export const LuaLS: Info = {
         const ok = await Archive.extractZip(tempPath, installDir)
           .then(() => true)
           .catch((error) => {
-            log.error("Failed to extract lua-language-server archive", { error })
             return false
           })
         if (!ok) return
@@ -1572,7 +1484,6 @@ export const LuaLS: Info = {
         const ok = await run(["tar", "-xzf", tempPath, "-C", installDir])
           .then((result) => result.code === 0)
           .catch((error: unknown) => {
-            log.error("Failed to extract lua-language-server archive", { error })
             return false
           })
         if (!ok) return
@@ -1584,7 +1495,6 @@ export const LuaLS: Info = {
       bin = path.join(installDir, "bin", "lua-language-server" + (platform === "win32" ? ".exe" : ""))
 
       if (!(await Filesystem.exists(bin))) {
-        log.error("Failed to extract lua-language-server binary")
         return
       }
 
@@ -1593,15 +1503,11 @@ export const LuaLS: Info = {
           .chmod(bin, 0o755)
           .then(() => true)
           .catch((error: unknown) => {
-            log.error("Failed to set executable permission for lua-language-server binary", {
-              error,
-            })
             return false
           })
         if (!ok) return
       }
 
-      log.info(`installed lua-language-server`, { bin })
     }
 
     return {
@@ -1650,7 +1556,6 @@ export const Prisma: Info = {
   async spawn(root) {
     const prisma = which("prisma")
     if (!prisma) {
-      log.info("prisma not found, please install prisma")
       return
     }
     return {
@@ -1668,7 +1573,6 @@ export const Dart: Info = {
   async spawn(root) {
     const dart = which("dart")
     if (!dart) {
-      log.info("dart not found, please install dart first")
       return
     }
     return {
@@ -1686,7 +1590,6 @@ export const Ocaml: Info = {
   async spawn(root) {
     const bin = which("ocamllsp")
     if (!bin) {
-      log.info("ocamllsp not found, please install ocaml-lsp-server")
       return
     }
     return {
@@ -1731,11 +1634,9 @@ export const TerraformLS: Info = {
 
     if (!bin) {
       if (flags.disableLspDownload) return
-      log.info("downloading terraform-ls from HashiCorp releases")
 
       const releaseResponse = await fetch("https://api.releases.hashicorp.com/v1/releases/terraform-ls/latest")
       if (!releaseResponse.ok) {
-        log.error("Failed to fetch terraform-ls release info")
         return
       }
 
@@ -1753,13 +1654,11 @@ export const TerraformLS: Info = {
       const builds = release.builds ?? []
       const build = builds.find((b) => b.arch === tfArch && b.os === tfPlatform)
       if (!build?.url) {
-        log.error(`Could not find build for ${tfPlatform}/${tfArch} terraform-ls release version ${release.version}`)
         return
       }
 
       const downloadResponse = await fetch(build.url)
       if (!downloadResponse.ok) {
-        log.error("Failed to download terraform-ls")
         return
       }
 
@@ -1769,7 +1668,6 @@ export const TerraformLS: Info = {
       const ok = await Archive.extractZip(tempPath, Global.Path.bin)
         .then(() => true)
         .catch((error) => {
-          log.error("Failed to extract terraform-ls archive", { error })
           return false
         })
       if (!ok) return
@@ -1778,7 +1676,6 @@ export const TerraformLS: Info = {
       bin = path.join(Global.Path.bin, "terraform-ls" + (platform === "win32" ? ".exe" : ""))
 
       if (!(await Filesystem.exists(bin))) {
-        log.error("Failed to extract terraform-ls binary")
         return
       }
 
@@ -1786,7 +1683,6 @@ export const TerraformLS: Info = {
         await fs.chmod(bin, 0o755).catch(() => {})
       }
 
-      log.info(`installed terraform-ls`, { bin })
     }
 
     return {
@@ -1812,11 +1708,9 @@ export const TexLab: Info = {
 
     if (!bin) {
       if (flags.disableLspDownload) return
-      log.info("downloading texlab from GitHub releases")
 
       const response = await fetch("https://api.github.com/repos/latex-lsp/texlab/releases/latest")
       if (!response.ok) {
-        log.error("Failed to fetch texlab release info")
         return
       }
 
@@ -1826,7 +1720,6 @@ export const TexLab: Info = {
       }
       const version = release.tag_name?.replace("v", "")
       if (!version) {
-        log.error("texlab release did not include a version tag")
         return
       }
 
@@ -1841,13 +1734,11 @@ export const TexLab: Info = {
       const assets = release.assets ?? []
       const asset = assets.find((a) => a.name === assetName)
       if (!asset?.browser_download_url) {
-        log.error(`Could not find asset ${assetName} in texlab release`)
         return
       }
 
       const downloadResponse = await fetch(asset.browser_download_url)
       if (!downloadResponse.ok) {
-        log.error("Failed to download texlab")
         return
       }
 
@@ -1858,7 +1749,6 @@ export const TexLab: Info = {
         const ok = await Archive.extractZip(tempPath, Global.Path.bin)
           .then(() => true)
           .catch((error) => {
-            log.error("Failed to extract texlab archive", { error })
             return false
           })
         if (!ok) return
@@ -1872,7 +1762,6 @@ export const TexLab: Info = {
       bin = path.join(Global.Path.bin, "texlab" + (platform === "win32" ? ".exe" : ""))
 
       if (!(await Filesystem.exists(bin))) {
-        log.error("Failed to extract texlab binary")
         return
       }
 
@@ -1880,7 +1769,6 @@ export const TexLab: Info = {
         await fs.chmod(bin, 0o755).catch(() => {})
       }
 
-      log.info("installed texlab", { bin })
     }
 
     return {
@@ -1924,7 +1812,6 @@ export const Gleam: Info = {
   async spawn(root) {
     const gleam = which("gleam")
     if (!gleam) {
-      log.info("gleam not found, please install gleam first")
       return
     }
     return {
@@ -1945,7 +1832,6 @@ export const Clojure: Info = {
       bin = which("clojure-lsp.exe")
     }
     if (!bin) {
-      log.info("clojure-lsp not found, please install clojure-lsp first")
       return
     }
     return {
@@ -1973,7 +1859,6 @@ export const Nixd: Info = {
   async spawn(root) {
     const nixd = which("nixd")
     if (!nixd) {
-      log.info("nixd not found, please install nixd first")
       return
     }
     return {
@@ -1996,11 +1881,9 @@ export const Tinymist: Info = {
 
     if (!bin) {
       if (flags.disableLspDownload) return
-      log.info("downloading tinymist from GitHub releases")
 
       const response = await fetch("https://api.github.com/repos/Myriad-Dreamin/tinymist/releases/latest")
       if (!response.ok) {
-        log.error("Failed to fetch tinymist release info")
         return
       }
 
@@ -2032,13 +1915,11 @@ export const Tinymist: Info = {
       const assets = release.assets ?? []
       const asset = assets.find((a) => a.name === assetName)
       if (!asset?.browser_download_url) {
-        log.error(`Could not find asset ${assetName} in tinymist release`)
         return
       }
 
       const downloadResponse = await fetch(asset.browser_download_url)
       if (!downloadResponse.ok) {
-        log.error("Failed to download tinymist")
         return
       }
 
@@ -2049,7 +1930,6 @@ export const Tinymist: Info = {
         const ok = await Archive.extractZip(tempPath, Global.Path.bin)
           .then(() => true)
           .catch((error) => {
-            log.error("Failed to extract tinymist archive", { error })
             return false
           })
         if (!ok) return
@@ -2062,7 +1942,6 @@ export const Tinymist: Info = {
       bin = path.join(Global.Path.bin, "tinymist" + (platform === "win32" ? ".exe" : ""))
 
       if (!(await Filesystem.exists(bin))) {
-        log.error("Failed to extract tinymist binary")
         return
       }
 
@@ -2070,7 +1949,6 @@ export const Tinymist: Info = {
         await fs.chmod(bin, 0o755).catch(() => {})
       }
 
-      log.info("installed tinymist", { bin })
     }
 
     return {
@@ -2086,7 +1964,6 @@ export const HLS: Info = {
   async spawn(root) {
     const bin = which("haskell-language-server-wrapper")
     if (!bin) {
-      log.info("haskell-language-server-wrapper not found, please install haskell-language-server")
       return
     }
     return {
@@ -2104,7 +1981,6 @@ export const JuliaLS: Info = {
   async spawn(root) {
     const julia = which("julia")
     if (!julia) {
-      log.info("julia not found, please install julia first (https://julialang.org/downloads/)")
       return
     }
     return {

+ 1 - 34
packages/opencode/src/mcp/index.ts

@@ -15,7 +15,6 @@ import {
 } from "@modelcontextprotocol/sdk/types.js"
 import { Config } from "@/config/config"
 import { ConfigMCPV1 } from "@opencode-ai/core/v1/config/mcp"
-import * as Log from "@opencode-ai/core/util/log"
 import { NamedError } from "@opencode-ai/core/util/error"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 import { withTimeout } from "@/util/timeout"
@@ -33,7 +32,6 @@ import { InstanceState } from "@/effect/instance-state"
 import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
 import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
 
-const log = Log.create({ service: "mcp" })
 const DEFAULT_TIMEOUT = 30_000
 
 const TolerantListToolsResultSchema = ListToolsResultSchema.extend({
@@ -117,7 +115,6 @@ const sanitize = (s: string) => s.replace(/[^a-zA-Z0-9_-]/g, "_")
 
 function remoteURL(key: string, value: string) {
   if (URL.canParse(value)) return new URL(value)
-  log.warn("invalid remote mcp url", { key })
 }
 
 function isOutputSchemaValidationError(error: Error) {
@@ -135,7 +132,6 @@ function listTools(key: string, client: MCPClient, timeout: number) {
     Effect.catch((error) => {
       if (!isOutputSchemaValidationError(error)) return Effect.fail(error)
 
-      log.warn("failed to validate MCP tool output schemas, retrying without output schema validation", { key, error })
       return Effect.tryPromise({
         try: () =>
           client.request({ method: "tools/list" }, TolerantListToolsResultSchema, {
@@ -189,7 +185,6 @@ function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number
 function defs(key: string, client: MCPClient, timeout?: number) {
   return listTools(key, client, timeout ?? DEFAULT_TIMEOUT).pipe(
     Effect.catch((err) => {
-      log.error("failed to get tools from client", { key, error: err })
       return Effect.succeed(undefined)
     }),
   )
@@ -204,7 +199,6 @@ function fetchFromClient<T extends { name: string }>(
   return Effect.tryPromise({
     try: () => listFn(client),
     catch: (e: any) => {
-      log.warn(`failed to get ${label}`, { clientName, error: e.message })
       return e
     },
   }).pipe(
@@ -330,9 +324,7 @@ export const layer = Layer.effect(
             redirectUri: oauthConfig?.redirectUri,
           },
           {
-            onRedirect: async (url) => {
-              log.info("oauth redirect requested", { key, url: url.toString() })
-            },
+            onRedirect: async () => {},
           },
           auth,
         )
@@ -367,8 +359,6 @@ export const layer = Layer.effect(
               error instanceof UnauthorizedError || (authProvider && lastError.message.includes("OAuth"))
 
             if (isAuthError) {
-              log.info("mcp server requires authentication", { key, transport: name })
-
               if (lastError.message.includes("registration") || lastError.message.includes("client_id")) {
                 lastStatus = {
                   status: "needs_client_registration" as const,
@@ -396,18 +386,11 @@ export const layer = Layer.effect(
               }
             }
 
-            log.debug("transport connection failed", {
-              key,
-              transport: name,
-              url: mcp.url,
-              error: lastError.message,
-            })
             lastStatus = { status: "failed" as const, error: lastError.message }
             return Effect.succeed(undefined)
           }),
         )
         if (result) {
-          log.info("connected", { key, transport: result.transportName })
           return { client: result.client as MCPClient | undefined, status: { status: "connected" } as Status }
         }
         // If this was an auth error, stop trying other transports
@@ -437,9 +420,6 @@ export const layer = Layer.effect(
           ...mcp.environment,
         },
       })
-      transport.stderr?.on("data", (chunk: Buffer) => {
-        log.info(`mcp stderr: ${chunk.toString()}`, { key })
-      })
 
       const connectTimeout = mcp.timeout ?? DEFAULT_TIMEOUT
       return yield* connectTransport(transport, connectTimeout).pipe(
@@ -449,7 +429,6 @@ export const layer = Layer.effect(
         })),
         Effect.catch((error): Effect.Effect<{ client: MCPClient | undefined; status: Status }> => {
           const msg = error instanceof Error ? error.message : String(error)
-          log.error("local mcp startup failed", { key, command: mcp.command, cwd, error: msg })
           return Effect.succeed({ client: undefined, status: { status: "failed", error: msg } })
         }),
       )
@@ -457,11 +436,9 @@ export const layer = Layer.effect(
 
     const create = Effect.fn("MCP.create")(function* (key: string, mcp: ConfigMCPV1.Info) {
       if (mcp.enabled === false) {
-        log.info("mcp server disabled", { key })
         return DISABLED_RESULT
       }
 
-      log.info("found", { key, type: mcp.type })
 
       const { client: mcpClient, status } =
         mcp.type === "remote"
@@ -478,7 +455,6 @@ export const layer = Layer.effect(
         return { status: { status: "failed", error: "Failed to get tools" } } satisfies CreateResult
       }
 
-      log.info("create() successfully created client", { key, toolCount: listed.length })
       return { mcpClient, status, defs: listed } satisfies CreateResult
     })
     const cfgSvc = yield* Config.Service
@@ -510,7 +486,6 @@ export const layer = Layer.effect(
     function watch(s: State, name: string, client: MCPClient, bridge: EffectBridge.Shape, timeout?: number) {
       if (!client.getServerCapabilities()?.tools) return
       client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
-        log.info("tools list changed notification received", { server: name })
         if (s.clients[name] !== client || s.status[name]?.status !== "connected") return
 
         const listed = await bridge.promise(defs(name, client, timeout))
@@ -539,7 +514,6 @@ export const layer = Layer.effect(
           ([key, mcp]) =>
             Effect.gen(function* () {
               if (!isMcpConfigured(mcp)) {
-                log.error("Ignoring MCP config entry without type", { key })
                 return
               }
 
@@ -690,7 +664,6 @@ export const layer = Layer.effect(
 
             const listed = s.defs[clientName]
             if (!listed) {
-              log.warn("missing cached tools for connected server", { clientName })
               return
             }
 
@@ -745,13 +718,11 @@ export const layer = Layer.effect(
       const s = yield* InstanceState.get(state)
       const client = s.clients[clientName]
       if (!client) {
-        log.warn(`client not found for ${label}`, { clientName })
         return undefined
       }
       return yield* Effect.tryPromise({
         try: () => fn(client),
         catch: (e: any) => {
-          log.error(`failed to ${label}`, { clientName, ...meta, error: e?.message })
           return e
         },
       }).pipe(Effect.orElseSucceed(() => undefined))
@@ -873,7 +844,6 @@ export const layer = Layer.effect(
         return yield* storeClient(s, mcpName, client, listed, mcpConfig.timeout)
       }
 
-      log.info("opening browser for oauth", { mcpName, url: result.authorizationUrl, state: result.oauthState })
 
       const callbackPromise = McpOAuthCallback.waitForCallback(result.oauthState, mcpName)
 
@@ -894,7 +864,6 @@ export const layer = Layer.effect(
           }),
         ),
         Effect.catch(() => {
-          log.warn("failed to open browser, user must open URL manually", { mcpName })
           return events.publish(BrowserOpenFailed, { mcpName, url: result.authorizationUrl }).pipe(Effect.ignore)
         }),
       )
@@ -918,7 +887,6 @@ export const layer = Layer.effect(
       const result = yield* Effect.tryPromise({
         try: () => transport.finishAuth(authorizationCode).then(() => true as const),
         catch: (error) => {
-          log.error("failed to finish oauth", { mcpName, error })
           return error
         },
       }).pipe(Effect.option)
@@ -939,7 +907,6 @@ export const layer = Layer.effect(
       yield* auth.remove(mcpName)
       McpOAuthCallback.cancelPending(mcpName)
       pendingOAuthTransports.delete(mcpName)
-      log.info("removed oauth credentials", { mcpName })
     })
 
     const supportsOAuth = Effect.fn("MCP.supportsOAuth")(function* (mcpName: string) {

+ 0 - 9
packages/opencode/src/mcp/oauth-callback.ts

@@ -1,9 +1,7 @@
 import { createConnection } from "net"
 import { createServer } from "http"
-import * as Log from "@opencode-ai/core/util/log"
 import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH, parseRedirectUri } from "./oauth-provider"
 
-const log = Log.create({ service: "mcp.oauth-callback" })
 
 // Current callback server configuration (may differ from defaults if custom redirectUri is used)
 let currentPort = OAUTH_CALLBACK_PORT
@@ -87,12 +85,10 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http").
   const error = url.searchParams.get("error")
   const errorDescription = url.searchParams.get("error_description")
 
-  log.info("received oauth callback", { hasCode: !!code, state, error })
 
   // Enforce state parameter presence
   if (!state) {
     const errorMsg = "Missing required state parameter - potential CSRF attack"
-    log.error("oauth callback missing state parameter", { url: url.toString() })
     res.writeHead(400, { "Content-Type": "text/html" })
     res.end(HTML_ERROR(errorMsg))
     return
@@ -121,7 +117,6 @@ function handleRequest(req: import("http").IncomingMessage, res: import("http").
   // Validate state parameter
   if (!pendingAuths.has(state)) {
     const errorMsg = "Invalid or expired state parameter - potential CSRF attack"
-    log.error("oauth callback with invalid state", { state, pendingStates: Array.from(pendingAuths.keys()) })
     res.writeHead(400, { "Content-Type": "text/html" })
     res.end(HTML_ERROR(errorMsg))
     return
@@ -144,7 +139,6 @@ export async function ensureRunning(redirectUri?: string): Promise<void> {
 
   // If server is running on a different port/path, stop it first
   if (server && (currentPort !== port || currentPath !== path)) {
-    log.info("stopping oauth callback server to reconfigure", { oldPort: currentPort, newPort: port })
     await stop()
   }
 
@@ -152,7 +146,6 @@ export async function ensureRunning(redirectUri?: string): Promise<void> {
 
   const running = await isPortInUse(port)
   if (running) {
-    log.info("oauth callback server already running on another instance", { port })
     return
   }
 
@@ -162,7 +155,6 @@ export async function ensureRunning(redirectUri?: string): Promise<void> {
   server = createServer(handleRequest)
   await new Promise<void>((resolve, reject) => {
     server!.listen(currentPort, () => {
-      log.info("oauth callback server started", { port: currentPort, path: currentPath })
       resolve()
     })
     server!.on("error", reject)
@@ -214,7 +206,6 @@ export async function stop(): Promise<void> {
   if (server) {
     await new Promise<void>((resolve) => server!.close(() => resolve()))
     server = undefined
-    log.info("oauth callback server stopped")
   }
 
   for (const [_name, pending] of pendingAuths) {

+ 0 - 10
packages/opencode/src/mcp/oauth-provider.ts

@@ -7,9 +7,7 @@ import type {
 } from "@modelcontextprotocol/sdk/shared/auth.js"
 import { Effect } from "effect"
 import { McpAuth } from "./auth"
-import * as Log from "@opencode-ai/core/util/log"
 
-const log = Log.create({ service: "mcp.oauth" })
 
 const OAUTH_CALLBACK_PORT = 19876
 const OAUTH_CALLBACK_PATH = "/mcp/oauth/callback"
@@ -70,7 +68,6 @@ export class McpOAuthProvider implements OAuthClientProvider {
     if (entry?.clientInfo) {
       // Check if client secret has expired
       if (entry.clientInfo.clientSecretExpiresAt && entry.clientInfo.clientSecretExpiresAt < Date.now() / 1000) {
-        log.info("client secret expired, need to re-register", { mcpName: this.mcpName })
         return undefined
       }
       return {
@@ -96,10 +93,6 @@ export class McpOAuthProvider implements OAuthClientProvider {
         this.serverUrl,
       ),
     )
-    log.info("saved dynamically registered client", {
-      mcpName: this.mcpName,
-      clientId: info.client_id,
-    })
   }
 
   async tokens(): Promise<OAuthTokens | undefined> {
@@ -131,11 +124,9 @@ export class McpOAuthProvider implements OAuthClientProvider {
         this.serverUrl,
       ),
     )
-    log.info("saved oauth tokens", { mcpName: this.mcpName })
   }
 
   async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
-    log.info("redirecting to authorization", { mcpName: this.mcpName, url: authorizationUrl.toString() })
     await this.callbacks.onRedirect(authorizationUrl)
   }
 
@@ -173,7 +164,6 @@ export class McpOAuthProvider implements OAuthClientProvider {
   }
 
   async invalidateCredentials(type: "all" | "client" | "tokens"): Promise<void> {
-    log.info("invalidating credentials", { mcpName: this.mcpName, type })
     const entry = await Effect.runPromise(this.auth.get(this.mcpName))
     if (!entry) {
       return

+ 0 - 1
packages/opencode/src/node.ts

@@ -1,5 +1,4 @@
 export { Config } from "@/config/config"
 export { Server } from "./server/server"
 export { bootstrap } from "./cli/bootstrap"
-export * as Log from "@opencode-ai/core/util/log"
 export { Database } from "@opencode-ai/core/database/database"

+ 4 - 7
packages/opencode/src/patch/index.ts

@@ -1,11 +1,8 @@
 import { Effect, Schema } from "effect"
 import * as path from "path"
 import { FSUtil } from "@opencode-ai/core/fs-util"
-import * as Log from "@opencode-ai/core/util/log"
 import * as Bom from "../util/bom"
 
-const log = Log.create({ service: "patch" })
-
 export const PatchSchema = Schema.Struct({
   patchText: Schema.String.annotate({ description: "The full patch text that describes all changes to be made" }),
 })
@@ -530,14 +527,14 @@ export const applyHunksToFiles = Effect.fn("Patch.applyHunksToFiles")(function*
       case "add": {
         yield* fs.writeWithDirs(hunk.path, hunk.contents)
         added.push(hunk.path)
-        log.info(`Added file: ${hunk.path}`)
+        yield* Effect.logInfo(`Added file: ${hunk.path}`)
         break
       }
 
       case "delete": {
         yield* fs.remove(hunk.path)
         deleted.push(hunk.path)
-        log.info(`Deleted file: ${hunk.path}`)
+        yield* Effect.logInfo(`Deleted file: ${hunk.path}`)
         break
       }
 
@@ -549,11 +546,11 @@ export const applyHunksToFiles = Effect.fn("Patch.applyHunksToFiles")(function*
           yield* fs.writeWithDirs(hunk.move_path, Bom.join(fileUpdate.content, fileUpdate.bom))
           yield* fs.remove(hunk.path)
           modified.push(hunk.move_path)
-          log.info(`Moved file: ${hunk.path} -> ${hunk.move_path}`)
+          yield* Effect.logInfo(`Moved file: ${hunk.path} -> ${hunk.move_path}`)
         } else {
           yield* fs.writeWithDirs(hunk.path, Bom.join(fileUpdate.content, fileUpdate.bom))
           modified.push(hunk.path)
-          log.info(`Updated file: ${hunk.path}`)
+          yield* Effect.logInfo(`Updated file: ${hunk.path}`)
         }
         break
       }

+ 2 - 5
packages/opencode/src/permission/index.ts

@@ -1,6 +1,5 @@
 import { ConfigPermissionV1 } from "@opencode-ai/core/v1/config/permission"
 import { InstanceState } from "@/effect/instance-state"
-import * as Log from "@opencode-ai/core/util/log"
 import { Wildcard } from "@opencode-ai/core/util/wildcard"
 import { Deferred, Effect, Layer, Context } from "effect"
 import os from "os"
@@ -8,8 +7,6 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { EventV2 } from "@opencode-ai/core/event"
 
-const log = Log.create({ service: "permission" })
-
 export const Event = {
   Asked: EventV2.define({ type: "permission.asked", schema: PermissionV1.Request.fields }),
   Replied: EventV2.define({
@@ -84,7 +81,7 @@ export const layer = Layer.effect(
 
       for (const pattern of request.patterns) {
         const rule = evaluate(request.permission, pattern, ruleset, approved)
-        log.info("evaluated", { permission: request.permission, pattern, action: rule })
+        yield* Effect.logInfo("evaluated", { permission: request.permission, pattern, action: rule })
         if (rule.action === "deny") {
           return yield* new PermissionV1.DeniedError({
             ruleset: ruleset.filter((rule) => Wildcard.match(request.permission, rule.permission)),
@@ -106,7 +103,7 @@ export const layer = Layer.effect(
         always: request.always,
         tool: request.tool,
       }
-      log.info("asking", { id, permission: info.permission, patterns: info.patterns })
+      yield* Effect.logInfo("asking", { id, permission: info.permission, patterns: info.patterns })
 
       const deferred = yield* Deferred.make<void, PermissionV1.RejectedError | PermissionV1.CorrectedError>()
       pending.set(id, { info, deferred })

+ 2 - 9
packages/opencode/src/plugin/digitalocean.ts

@@ -1,11 +1,9 @@
 import type { Hooks, PluginInput } from "@opencode-ai/plugin"
 import type { Model } from "@opencode-ai/sdk/v2"
-import * as Log from "@opencode-ai/core/util/log"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 import { createServer } from "http"
 import open from "open"
 
-const log = Log.create({ service: "plugin.digitalocean" })
 
 const DO_OAUTH_CLIENT_ID = "b1a6c5158156caac821fd1b30253ca8acb52454a48fa744420e41889cb589f82"
 const DO_AUTHORIZE_URL = "https://cloud.digitalocean.com/v1/oauth/authorize"
@@ -188,7 +186,6 @@ async function startOAuthServer(): Promise<void> {
 
   await new Promise<void>((resolve, reject) => {
     oauthServer!.listen(OAUTH_PORT, () => {
-      log.info("digitalocean oauth server started", { port: OAUTH_PORT })
       resolve()
     })
     oauthServer!.on("error", reject)
@@ -197,7 +194,7 @@ async function startOAuthServer(): Promise<void> {
 
 function stopOAuthServer() {
   if (!oauthServer) return
-  oauthServer.close(() => log.info("digitalocean oauth server stopped"))
+  oauthServer.close()
   oauthServer = undefined
 }
 
@@ -315,11 +312,9 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise<Hooks>
                 path: { id: "digitalocean" },
                 body: { type: "api", key: ctx.auth.key, metadata: updated },
               })
-              .catch((err) => log.warn("failed to persist refreshed routers", { error: err }))
+              .catch(() => {})
           } else if (result.status === 401 || result.status === 403) {
-            log.warn("digitalocean oauth bearer rejected; using cached routers", { status: result.status })
           } else if (result.status !== 0) {
-            log.warn("digitalocean router refresh failed", { status: result.status })
           }
         }
 
@@ -355,7 +350,6 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise<Hooks>
                   const routerResult = await listRouters(tokens.access_token)
                   const routers = routerResult.ok ? routerResult.routers : []
                   if (!routerResult.ok) {
-                    log.warn("digitalocean initial router fetch failed", { status: routerResult.status })
                   }
                   return {
                     type: "success" as const,
@@ -372,7 +366,6 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise<Hooks>
                     },
                   }
                 } catch (err) {
-                  log.error("digitalocean oauth callback failed", { error: err })
                   return { type: "failed" as const }
                 } finally {
                   stopOAuthServer()

+ 0 - 3
packages/opencode/src/plugin/github-copilot/copilot.ts

@@ -2,12 +2,10 @@ import type { Hooks, PluginInput } from "@opencode-ai/plugin"
 import type { Model } from "@opencode-ai/sdk/v2"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 import { iife } from "@/util/iife"
-import * as Log from "@opencode-ai/core/util/log"
 import { setTimeout as sleep } from "node:timers/promises"
 import { CopilotModels } from "./models"
 import { MessageV2 } from "@/session/message-v2"
 
-const log = Log.create({ service: "plugin.copilot" })
 
 const CLIENT_ID = "Ov23li8tweQw6odWQebz"
 const API_VERSION = "2026-06-01"
@@ -87,7 +85,6 @@ export async function CopilotAuthPlugin(input: PluginInput): Promise<Hooks> {
           })
           .catch((error) => {
             models = {}
-            log.error("failed to fetch copilot models", { error })
             return Object.fromEntries(
               Object.entries(provider.models).map(([id, model]) => [id, fix(model, base(auth.enterpriseUrl))]),
             )

+ 5 - 17
packages/opencode/src/plugin/index.ts

@@ -6,7 +6,6 @@ import type {
   WorkspaceAdapter as PluginWorkspaceAdapter,
 } from "@opencode-ai/plugin"
 import { Config } from "@/config/config"
-import * as Log from "@opencode-ai/core/util/log"
 import { createOpencodeClient } from "@opencode-ai/sdk"
 import { ServerAuth } from "@/server/auth"
 import { CodexAuthPlugin } from "./openai/codex"
@@ -31,7 +30,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { InstallationChannel } from "@opencode-ai/core/installation/version"
 
-const log = Log.create({ service: "plugin" })
 
 type State = {
   hooks: Hooks[]
@@ -163,11 +161,9 @@ export const layer = Layer.effect(
         }
 
         for (const plugin of flags.disableDefaultPlugins ? [] : internalPlugins(flags)) {
-          log.info("loading internal plugin", { name: plugin.name })
           const init = yield* Effect.tryPromise({
             try: () => plugin(input),
             catch: (err) => {
-              log.error("failed to load internal plugin", { name: plugin.name, error: err })
             },
           }).pipe(Effect.option)
           if (init._tag === "Some") hooks.push(init.value)
@@ -175,7 +171,6 @@ export const layer = Layer.effect(
 
         const plugins = flags.pure ? [] : (cfg.plugin_origins ?? [])
         if (flags.pure && cfg.plugin_origins?.length) {
-          log.info("skipping external plugins in pure mode", { count: cfg.plugin_origins.length })
         }
         if (plugins.length) yield* config.waitForDependencies()
 
@@ -185,10 +180,8 @@ export const layer = Layer.effect(
             kind: "server",
             report: {
               start(candidate) {
-                log.info("loading plugin", { path: candidate.plan.spec })
               },
               missing(candidate, _retry, message) {
-                log.warn("plugin has no server entrypoint", { path: candidate.plan.spec, message })
               },
               error(candidate, _retry, stage, error, resolved) {
                 const spec = candidate.plan.spec
@@ -197,24 +190,20 @@ export const layer = Layer.effect(
 
                 if (stage === "install") {
                   const parsed = parsePluginSpecifier(spec)
-                  log.error("failed to install plugin", { pkg: parsed.pkg, version: parsed.version, error: message })
                   publishPluginError(`Failed to install plugin ${parsed.pkg}@${parsed.version}: ${message}`)
                   return
                 }
 
                 if (stage === "compatibility") {
-                  log.warn("plugin incompatible", { path: spec, error: message })
                   publishPluginError(`Plugin ${spec} skipped: ${message}`)
                   return
                 }
 
                 if (stage === "entry") {
-                  log.error("failed to resolve plugin server entry", { path: spec, error: message })
                   publishPluginError(`Failed to load plugin ${spec}: ${message}`)
                   return
                 }
 
-                log.error("failed to load plugin", { path: spec, target: resolved?.entry, error: message })
                 publishPluginError(`Failed to load plugin ${spec}: ${message}`)
               },
             },
@@ -229,7 +218,6 @@ export const layer = Layer.effect(
             try: () => applyPlugin(load, input, hooks),
             catch: (err) => {
               const message = errorMessage(err)
-              log.error("failed to load plugin", { path: load.spec, error: message })
               return message
             },
           }).pipe(
@@ -249,10 +237,11 @@ export const layer = Layer.effect(
         for (const hook of hooks) {
           yield* Effect.tryPromise({
             try: () => Promise.resolve((hook as any).config?.(cfg)),
-            catch: (err) => {
-              log.error("plugin config hook failed", { error: err })
-            },
-          }).pipe(Effect.ignore)
+            catch: errorMessage,
+          }).pipe(
+            Effect.tapError((error) => Effect.logError("plugin config hook failed", { error })),
+            Effect.ignore,
+          )
         }
 
         const unsubscribe = yield* events.listen((event) => {
@@ -272,7 +261,6 @@ export const layer = Layer.effect(
               Effect.tryPromise({
                 try: () => Promise.resolve(hook.dispose?.()),
                 catch: (error) => {
-                  log.error("plugin dispose hook failed", { error })
                 },
               }).pipe(Effect.ignore),
             { discard: true },

+ 0 - 5
packages/opencode/src/plugin/openai/codex.ts

@@ -1,5 +1,4 @@
 import type { Hooks, PluginInput } from "@opencode-ai/plugin"
-import * as Log from "@opencode-ai/core/util/log"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 import { OAUTH_DUMMY_KEY } from "../../auth"
 import os from "os"
@@ -7,7 +6,6 @@ import { setTimeout as sleep } from "node:timers/promises"
 import { createServer } from "http"
 import { OpenAIWebSocketPool } from "./ws-pool"
 
-const log = Log.create({ service: "plugin.codex" })
 
 const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
 const ISSUER = "https://auth.openai.com"
@@ -306,7 +304,6 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string }
 
   await new Promise<void>((resolve, reject) => {
     oauthServer!.listen(OAUTH_PORT, () => {
-      log.info("codex oauth server started", { port: OAUTH_PORT })
       resolve()
     })
     oauthServer!.on("error", reject)
@@ -318,7 +315,6 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string }
 function stopOAuthServer() {
   if (oauthServer) {
     oauthServer.close(() => {
-      log.info("codex oauth server stopped")
     })
     oauthServer = undefined
   }
@@ -442,7 +438,6 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
 
             if (!currentAuth.access || currentAuth.expires < Date.now()) {
               if (!refreshPromise) {
-                log.info("refreshing codex access token")
                 refreshPromise = refreshAccessToken(currentAuth.refresh, issuer)
                   .then(async (tokens) => {
                     const accountId = extractAccountId(tokens) || authWithAccount.accountId

+ 0 - 19
packages/opencode/src/plugin/openai/ws-pool.ts

@@ -1,12 +1,10 @@
 import WebSocket from "ws"
-import * as Log from "@opencode-ai/core/util/log"
 import { ProviderError } from "@/provider/error"
 import { isRecord } from "@/util/record"
 import { OpenAIWebSocket } from "./ws"
 
 export const TITLE_HEADER = "x-opencode-title"
 
-const log = Log.create({ service: "plugin.openai.ws" })
 
 export interface CreateWebSocketFetchOptions {
   httpFetch?: typeof globalThis.fetch
@@ -63,13 +61,11 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
     })()
     if (!body?.stream) return httpFetch(input, httpInit)
     if (internalHeaders[TITLE_HEADER] === "true") {
-      log.debug("http fallback", { reason: "title" })
       return httpFetch(input, httpInit)
     }
 
     const sessionID = internalHeaders["x-session-affinity"] ?? internalHeaders["session-id"]
     if (!sessionID) {
-      log.debug("http fallback", { reason: "missing_session" })
       return httpFetch(input, httpInit)
     }
     const key = `${sessionID}:conversation`
@@ -78,11 +74,9 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
     pool.set(key, entry)
 
     if (entry.fallback) {
-      log.debug("http fallback", { key, reason: "fallback_active" })
       return httpFetch(input, httpInit)
     }
     if (entry.busy) {
-      log.debug("http fallback", { key, reason: "busy" })
       return httpFetch(input, httpInit)
     }
 
@@ -114,12 +108,10 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
           entry.lastUsedAt = Date.now()
           entry.streamFailures = 0
           if (event.type !== "response.completed" && event.type !== "response.done") {
-            log.warn("websocket terminal failure", { key, type: event.type })
             invalidate(entry)
           }
         },
         onConnectionInvalid: (error) => {
-          log.warn("websocket invalidated", { key, error: error.message })
           entry.busy = false
           entry.lastUsedAt = Date.now()
           if (!entry.fallback) recordStreamFailure(entry)
@@ -127,7 +119,6 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
           resolveFirstEvent(false)
         },
         onAbort: (error) => {
-          log.debug("websocket aborted", { key })
           entry.busy = false
           entry.lastUsedAt = Date.now()
           entry.streamFailures = 0
@@ -137,7 +128,6 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
         onRetryableTerminal: async (event) => {
           const error = connectionLimitError(event)
           if (!error) return undefined
-          log.warn("websocket connection limit reached", { key })
           throw error
         },
       })
@@ -150,7 +140,6 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
         })
       }
       if (!entry.fallback) return response
-      log.debug("http fallback", { key, reason: "websocket_retries_exhausted" })
       return httpFetch(input, httpInit)
     } catch (error) {
       entry.busy = false
@@ -162,11 +151,6 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
       }
 
       recordStreamFailure(entry)
-      log.warn("websocket setup failed", {
-        key,
-        error: error instanceof Error ? error.message : String(error),
-        fallback: entry.fallback ? "http" : undefined,
-      })
       invalidate(entry)
       if (entry.fallback) return httpFetch(input, httpInit)
       return failedResponse(
@@ -189,14 +173,12 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
       if (entry.busy) continue
       if (entry.fallback) continue
       if (now - entry.lastUsedAt < idleTimeout) continue
-      log.debug("websocket idle prune", { key })
       invalidate(entry)
       pool.delete(key)
     }
   }
 
   function close() {
-    log.debug("websocket pool close", { count: pool.size })
     clearInterval(pruneTimer)
     for (const entry of pool.values()) invalidate(entry)
     pool.clear()
@@ -206,7 +188,6 @@ export function createWebSocketFetch(options?: CreateWebSocketFetchOptions) {
     const key = `${sessionID}:conversation`
     const entry = pool.get(key)
     if (!entry) return
-    log.debug("websocket pool remove", { key })
     invalidate(entry)
     pool.delete(key)
   }

+ 8 - 52
packages/opencode/src/plugin/tui/runtime.ts

@@ -14,7 +14,6 @@ import {
 import path from "path"
 import { fileURLToPath } from "url"
 import { TuiConfig } from "@/config/tui"
-import * as Log from "@opencode-ai/core/util/log"
 import { errorData, errorMessage } from "@opencode-ai/tui/util/error"
 import { isRecord } from "@opencode-ai/tui/util/record"
 import { resolveHostAttentionSoundPaths } from "@/config/tui-host-attention"
@@ -119,7 +118,6 @@ type RuntimeState = {
   dispose_timeout_ms: number
 }
 
-const log = Log.create({ service: "tui.plugin" })
 const DISPOSE_TIMEOUT_MS = 5000
 const KV_KEY = "plugin_enabled"
 const EMPTY_TUI: TuiPluginModule = {
@@ -128,19 +126,16 @@ const EMPTY_TUI: TuiPluginModule = {
 
 function fail(message: string, data: Record<string, unknown>) {
   if (!("error" in data)) {
-    log.error(message, data)
     console.error(`[tui.plugin] ${message}`, data)
     return
   }
 
   const text = `${message}: ${errorMessage(data.error)}`
   const next = { ...data, error: errorData(data.error) }
-  log.error(text, next)
   console.error(`[tui.plugin] ${text}`, next)
 }
 
 function warn(message: string, data: Record<string, unknown>) {
-  log.warn(message, data)
   console.warn(`[tui.plugin] ${message}`, data)
 }
 
@@ -275,15 +270,7 @@ function createThemeInstaller(
     await Flock.withLock(`tui-theme:${dest}`, async () => {
       const save = async () => {
         plugin.themes[name] = info
-        await PluginMeta.setTheme(plugin.id, name, info).catch((error) => {
-          log.warn("failed to track tui plugin theme", {
-            path: spec,
-            id: plugin.id,
-            theme: src,
-            dest,
-            error,
-          })
-        })
+        await PluginMeta.setTheme(plugin.id, name, info).catch(() => {})
       }
 
       const exists = hasTheme(name)
@@ -298,37 +285,26 @@ function createThemeInstaller(
         if (prev?.dest === dest && prev.mtime === mtime && prev.size === size) return
       }
 
-      const text = await Filesystem.readText(src).catch((error) => {
-        log.warn("failed to read tui plugin theme", { path: spec, theme: src, error })
-        return
-      })
+      const text = await Filesystem.readText(src).catch(() => undefined)
       if (text === undefined) return
 
       const fail = Symbol()
       const data = await Promise.resolve(text)
         .then((x) => JSON.parse(x))
-        .catch((error) => {
-          log.warn("failed to parse tui plugin theme", { path: spec, theme: src, error })
-          return fail
-        })
+        .catch(() => fail)
       if (data === fail) return
 
       if (!isTheme(data)) {
-        log.warn("invalid tui plugin theme", { path: spec, theme: src })
         return
       }
 
       if (exists || !(await Filesystem.exists(dest))) {
-        await Filesystem.write(dest, text).catch((error) => {
-          log.warn("failed to persist tui plugin theme", { path: spec, theme: src, dest, error })
-        })
+        await Filesystem.write(dest, text).catch(() => {})
       }
 
       upsertTheme(name, data)
       await save()
-    }).catch((error) => {
-      log.warn("failed to lock tui plugin theme install", { path: spec, theme: src, dest, error })
-    })
+    }).catch(() => {})
   }
 }
 
@@ -701,9 +677,7 @@ async function resolveExternalPlugins(list: ConfigPlugin.Origin[], wait: () => P
     items: list,
     kind: "tui",
     wait: async () => {
-      await wait().catch((error) => {
-        log.warn("failed waiting for tui plugin dependencies", { error })
-      })
+      await wait().catch(() => {})
     },
     finish: async (loaded, origin, retry) => {
       const mod = await Promise.resolve()
@@ -774,9 +748,7 @@ async function resolveExternalPlugins(list: ConfigPlugin.Origin[], wait: () => P
       }
     },
     report: {
-      start(candidate, retry) {
-        log.info("loading tui plugin", { path: candidate.plan.spec, retry })
-      },
+      start() {},
       missing(candidate, retry, message) {
         warn("tui plugin has no entrypoint", { path: candidate.plan.spec, retry, message })
       },
@@ -809,10 +781,7 @@ async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[]
       target: item.target,
       id: item.id,
     })),
-  ).catch((error) => {
-    log.warn("failed to track tui plugins", { error })
-    return undefined
-  })
+  ).catch(() => undefined)
 
   const plugins: PluginEntry[] = []
   let ok = true
@@ -820,17 +789,6 @@ async function addExternalPluginEntries(state: RuntimeState, ready: PluginLoad[]
     const entry = ready[i]
     if (!entry) continue
     const hit = meta?.[i]
-    if (hit && hit.state !== "same") {
-      log.info("tui plugin metadata updated", {
-        path: entry.spec,
-        retry: entry.retry,
-        state: hit.state,
-        source: hit.entry.source,
-        version: hit.entry.version,
-        modified: hit.entry.modified,
-      })
-    }
-
     const info = createMeta(entry.source, entry.spec, entry.target, hit, entry.id)
     const themes = hit?.entry.themes ? { ...hit.entry.themes } : {}
     const plugin: PluginEntry = {
@@ -1129,11 +1087,9 @@ async function load(input: {
     const pluginOrigins = config.plugin_origins ?? (await TuiConfig.pluginOrigins())
     const records = Flag.OPENCODE_PURE ? [] : pluginOrigins
     if (Flag.OPENCODE_PURE && pluginOrigins.length) {
-      log.info("skipping external tui plugins in pure mode", { count: pluginOrigins.length })
     }
 
     for (const item of internalTuiPlugins(flags)) {
-      log.info("loading internal tui plugin", { id: item.id })
       const entry = loadInternalPlugin(item)
       const meta = createMeta(entry.source, entry.spec, entry.target, undefined, entry.id)
       addPluginEntry(next, {

+ 2 - 9
packages/opencode/src/plugin/xai.ts

@@ -1,10 +1,8 @@
 import type { Hooks, PluginInput } from "@opencode-ai/plugin"
-import * as Log from "@opencode-ai/core/util/log"
 import { OAUTH_DUMMY_KEY } from "../auth"
 import { createServer } from "http"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
 
-const log = Log.create({ service: "plugin.xai" })
 
 // Public Grok-CLI OAuth client. xAI's auth server rejects loopback OAuth from
 // non-allowlisted clients, so we reuse the Grok-CLI client_id that xAI ships
@@ -510,8 +508,6 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string }
       // behavior and crash the entire opencode process. Matches the silent-
       // swallow behavior the Codex plugin gets from its permanent
       // `oauthServer!.on("error", reject)`.
-      server.on("error", (err) => log.warn("xai oauth server error", { error: err }))
-      log.info("xai oauth server started", { host: OAUTH_HOST, port: OAUTH_PORT })
       resolve()
     })
     oauthServer = server
@@ -522,7 +518,7 @@ async function startOAuthServer(): Promise<{ port: number; redirectUri: string }
 
 function stopOAuthServer() {
   if (oauthServer) {
-    oauthServer.close(() => log.info("xai oauth server stopped"))
+    oauthServer.close()
     oauthServer = undefined
   }
 }
@@ -607,7 +603,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp
             if (expiresSoon) {
               if (!refreshPromise) {
                 const refreshToken = currentAuth.refresh
-                log.info("refreshing xai access token")
                 refreshPromise = refreshAccessToken(refreshToken, options)
                   .then(async (tokens) => {
                     const refreshedExpires = Date.now() + (tokens.expires_in ?? 3600) * 1000
@@ -627,7 +622,7 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp
                           expires: refreshedExpires,
                         },
                       })
-                      .catch((err) => log.warn("failed to persist refreshed xai tokens", { error: err }))
+                      .catch(() => {})
                     return { access: tokens.access_token, refresh: refreshedRefresh, expires: refreshedExpires }
                   })
                   .finally(() => {
@@ -688,7 +683,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp
                     expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
                   }
                 } catch (err) {
-                  log.error("xai oauth callback failed", { error: err })
                   return { type: "failed" as const }
                 } finally {
                   stopOAuthServer()
@@ -725,7 +719,6 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp
                     expires: Date.now() + (tokens.expires_in ?? 3600) * 1000,
                   }
                 } catch (err) {
-                  log.error("xai device code callback failed", { error: err })
                   return { type: "failed" as const }
                 }
               },

+ 1 - 1
packages/opencode/src/project/bootstrap.ts

@@ -39,7 +39,7 @@ export const layer = Layer.effect(
 
     const run = Effect.gen(function* () {
       const ctx = yield* InstanceState.context
-      yield* Effect.logInfo("bootstrapping").pipe(Effect.annotateLogs("directory", ctx.directory))
+      yield* Effect.logInfo("bootstrapping", { directory: ctx.directory })
       // everything depends on config so eager load it for nice traces
       yield* config.get()
       // in 99% of use cases user that is opened opencode at certain directory will

+ 4 - 6
packages/opencode/src/project/instance-store.ts

@@ -90,7 +90,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
       )
 
     const disposeContext = Effect.fn("InstanceStore.disposeContext")(function* (ctx: InstanceContext) {
-      yield* Effect.logInfo("disposing instance").pipe(Effect.annotateLogs("directory", ctx.directory))
+      yield* Effect.logInfo("disposing instance", { directory: ctx.directory })
       yield* Effect.promise(() => runDisposers(ctx.directory))
       yield* emitDisposed({ directory: ctx.directory, project: ctx.project.id })
     })
@@ -113,7 +113,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
           const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() }
           cache.set(directory, entry)
           yield* Effect.gen(function* () {
-            yield* Effect.logInfo("creating instance").pipe(Effect.annotateLogs("directory", directory))
+            yield* Effect.logInfo("creating instance", { directory: directory })
             yield* completeLoad(directory, input, entry)
           }).pipe(Effect.forkIn(scope, { startImmediately: true }))
           return yield* restore(Deferred.await(entry.deferred))
@@ -129,7 +129,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
           const entry: Entry = { deferred: Deferred.makeUnsafe<InstanceContext>() }
           cache.set(directory, entry)
           yield* Effect.gen(function* () {
-            yield* Effect.logInfo("reloading instance").pipe(Effect.annotateLogs("directory", directory))
+            yield* Effect.logInfo("reloading instance", { directory: directory })
             if (previous) {
               yield* Deferred.await(previous.deferred).pipe(Effect.ignore)
               yield* Effect.promise(() => runDisposers(directory))
@@ -169,9 +169,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
           Effect.gen(function* () {
             const exit = yield* Deferred.await(item[1].deferred).pipe(Effect.exit)
             if (Exit.isFailure(exit)) {
-              yield* Effect.logWarning("instance dispose failed").pipe(
-                Effect.annotateLogs({ key: item[0], cause: exit.cause }),
-              )
+              yield* Effect.logWarning("instance dispose failed", { key: item[0], cause: exit.cause })
               yield* removeEntry(item[0], item[1])
               return
             }

+ 2 - 5
packages/opencode/src/project/project.ts

@@ -3,7 +3,6 @@ import { Database } from "@opencode-ai/core/database/database"
 import { ProjectDirectoryTable, ProjectTable } from "@opencode-ai/core/project/sql"
 import { SessionTable } from "@opencode-ai/core/session/sql"
 import { WorkspaceTable } from "@opencode-ai/core/control-plane/workspace.sql"
-import * as Log from "@opencode-ai/core/util/log"
 import { Flag } from "@opencode-ai/core/flag/flag"
 import { GlobalBus } from "@/bus/global"
 import { which } from "@opencode-ai/core/util/which"
@@ -22,8 +21,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { EventV2 } from "@opencode-ai/core/event"
 
-const log = Log.create({ service: "project" })
-
 const ProjectVcs = Schema.Literal("git")
 
 const ProjectIcon = Schema.Struct({
@@ -246,13 +243,13 @@ export const layer = Layer.effect(
         )
         .pipe(
           Effect.catchCause((cause) =>
-            Effect.sync(() => log.warn("project directory persistence failed", { projectID: input.projectID, cause })),
+            Effect.logWarning("project directory persistence failed", { projectID: input.projectID, cause }),
           ),
         )
     })
 
     const fromDirectory = Effect.fn("Project.fromDirectory")(function* (directory: string) {
-      log.info("fromDirectory", { directory })
+      yield* Effect.logInfo("fromDirectory", { directory })
 
       const data = yield* projectV2.resolve(AbsolutePath.make(directory))
       const worktree = data.id === ProjectV2.ID.make("global") && !data.vcs ? "/" : data.directory

+ 0 - 7
packages/opencode/src/project/vcs.ts

@@ -3,11 +3,9 @@ import { formatPatch, structuredPatch } from "diff"
 import { InstanceState } from "@/effect/instance-state"
 import { Watcher } from "@opencode-ai/core/filesystem/watcher"
 import { Git } from "@/git"
-import * as Log from "@opencode-ai/core/util/log"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { EventV2 } from "@opencode-ai/core/event"
 
-const log = Log.create({ service: "vcs" })
 const PATCH_CONTEXT_LINES = 2_147_483_647
 const MAX_PATCH_BYTES = 10_000_000
 const MAX_TOTAL_PATCH_BYTES = 10_000_000
@@ -107,7 +105,6 @@ const batchPatches = Effect.fnUntraced(function* (
     context: options?.context ?? PATCH_CONTEXT_LINES,
     maxOutputBytes: MAX_TOTAL_PATCH_BYTES,
   })
-  if (result.truncated) log.warn("batched patch exceeded byte limit", { max: MAX_TOTAL_PATCH_BYTES })
 
   return {
     patches: splitGitPatch(result).reduce((acc, patch, index) => {
@@ -139,13 +136,11 @@ const nativePatch = Effect.fnUntraced(function* (
         })
   if (!result.truncated && result.text) return result.text
 
-  if (result.truncated) log.warn("patch exceeded byte limit", { file: item.file, max: MAX_PATCH_BYTES })
   return emptyPatch(item.file)
 })
 
 const totalPatch = (file: string, patch: string, total: number) => {
   if (total + Buffer.byteLength(patch) <= MAX_TOTAL_PATCH_BYTES) return { patch, capped: false }
-  log.warn("total patch budget exceeded", { file, max: MAX_TOTAL_PATCH_BYTES })
   return { patch: emptyPatch(file), capped: true }
 }
 
@@ -325,7 +320,6 @@ export const layer: Layer.Layer<Service, never, Git.Service | EventV2Bridge.Serv
           concurrency: 2,
         })
         const value = { current, root }
-        log.info("initialized", { branch: value.current, default_branch: value.root?.name })
 
         const unsubscribe = yield* events.listen((event) => {
           if (event.type !== Watcher.Event.Updated.type || event.location?.directory !== ctx.directory)
@@ -335,7 +329,6 @@ export const layer: Layer.Layer<Service, never, Git.Service | EventV2Bridge.Serv
           return Effect.gen(function* () {
             const next = yield* get()
             if (next !== value.current) {
-              log.info("branch changed", { from: value.current, to: next })
               value.current = next
               yield* events.publish(Event.BranchUpdated, { branch: next })
             }

+ 0 - 30
packages/opencode/src/provider/provider.ts

@@ -4,7 +4,6 @@ import fuzzysort from "fuzzysort"
 import { Config } from "@/config/config"
 import { mapValues, mergeDeep, omit, pickBy, sortBy } from "remeda"
 import { NoSuchModelError, type Provider as SDK } from "ai"
-import { Log } from "@opencode-ai/core/util/log"
 import { Npm } from "@opencode-ai/core/npm"
 import { Hash } from "@opencode-ai/core/util/hash"
 import { Plugin } from "../plugin"
@@ -32,7 +31,6 @@ import { ModelStatus } from "./model-status"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { ProviderError } from "./error"
 
-const log = Log.create({ service: "provider" })
 const OPENAI_HEADER_TIMEOUT_DEFAULT = 10_000
 
 function wrapSSE(res: Response, ms: number, ctl: AbortController) {
@@ -648,7 +646,6 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
         },
         async discoverModels(): Promise<Record<string, Model>> {
           if (!apiKey) {
-            log.info("gitlab model discovery skipped: no apiKey")
             return {}
           }
 
@@ -657,18 +654,9 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
             const getHeaders = (): Record<string, string> =>
               auth?.type === "api" ? { "PRIVATE-TOKEN": token } : { Authorization: `Bearer ${token}` }
 
-            log.info("gitlab model discovery starting", { instanceUrl })
             const result = await discoverWorkflowModels({ instanceUrl, getHeaders }, { workingDirectory: directory })
 
             if (!result.models.length) {
-              log.info("gitlab model discovery skipped: no models found", {
-                project: result.project
-                  ? {
-                      id: result.project.id,
-                      path: result.project.pathWithNamespace,
-                    }
-                  : null,
-              })
               return {}
             }
 
@@ -717,13 +705,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
               }
             }
 
-            log.info("gitlab model discovery complete", {
-              count: Object.keys(models).length,
-              models: Object.keys(models),
-            })
             return models
           } catch (e) {
-            log.warn("gitlab model discovery failed", { error: e })
             return {}
           }
         },
@@ -1298,7 +1281,6 @@ export const layer = Layer.effect(
 
     const state = yield* InstanceState.make<State>(() =>
       Effect.gen(function* () {
-        using _ = log.time("state")
         const bridge = yield* EffectBridge.make()
         const cfg = yield* config.get()
         const modelsDev = yield* modelsDevSvc.get()
@@ -1324,7 +1306,6 @@ export const layer = Layer.effect(
           get: (key: string) => env.get(key),
         }
 
-        log.info("init")
 
         function mergeProvider(providerID: ProviderV2.ID, provider: Partial<Info>) {
           const existing = providers[providerID]
@@ -1526,7 +1507,6 @@ export const layer = Layer.effect(
           if (disabled.has(providerID)) continue
           const data = database[providerID]
           if (!data) {
-            log.error("Provider does not exist in model list " + providerID)
             continue
           }
           const result = yield* fn(data)
@@ -1561,7 +1541,6 @@ export const layer = Layer.effect(
                 }
               }
             } catch (e) {
-              log.warn("state discovery error", { id: "gitlab", error: e })
             }
           })
         }
@@ -1614,7 +1593,6 @@ export const layer = Layer.effect(
             continue
           }
 
-          log.info("found", { providerID })
         }
 
         return {
@@ -1632,9 +1610,6 @@ export const layer = Layer.effect(
 
     async function resolveSDK(model: Model, s: State, envs: Record<string, string | undefined>) {
       try {
-        using _ = log.time("getSDK", {
-          providerID: model.providerID,
-        })
         const provider = s.providers[model.providerID]
         const options = { ...provider.options }
 
@@ -1752,10 +1727,6 @@ export const layer = Layer.effect(
 
         const bundledLoader = BUNDLED_PROVIDERS[model.api.npm]
         if (bundledLoader) {
-          log.info("using bundled provider", {
-            providerID: model.providerID,
-            pkg: model.api.npm,
-          })
           const factory = await bundledLoader()
           const loaded = factory({
             name: model.providerID,
@@ -1767,7 +1738,6 @@ export const layer = Layer.effect(
 
         const installedPath = await (async () => {
           if (model.api.npm.startsWith("file://")) {
-            log.info("loading local provider", { pkg: model.api.npm })
             return model.api.npm
           }
           const item = await Npm.add(model.api.npm)

+ 5 - 8
packages/opencode/src/question/index.ts

@@ -1,13 +1,10 @@
 import { Deferred, Effect, Layer, Schema, Context } from "effect"
 import { InstanceState } from "@/effect/instance-state"
 import { SessionID, MessageID } from "@/session/schema"
-import * as Log from "@opencode-ai/core/util/log"
 import { QuestionID } from "./schema"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { EventV2 } from "@opencode-ai/core/event"
 
-const log = Log.create({ service: "question" })
-
 // Schemas — these are pure data; nothing checks class identity (see PR
 // description) so they're plain `Schema.Struct` + type alias. That lets
 // `Question.ask` and other internal sites trust the type contract without a
@@ -159,7 +156,7 @@ export const layer = Layer.effect(
     }) {
       const pending = (yield* InstanceState.get(state)).pending
       const id = QuestionID.ascending()
-      log.info("asking", { id, questions: input.questions.length })
+      yield* Effect.logInfo("asking", { id, questions: input.questions.length })
 
       const deferred = yield* Deferred.make<ReadonlyArray<Answer>, RejectedError>()
       const info: Request = {
@@ -186,11 +183,11 @@ export const layer = Layer.effect(
       const pending = (yield* InstanceState.get(state)).pending
       const existing = pending.get(input.requestID)
       if (!existing) {
-        log.warn("reply for unknown request", { requestID: input.requestID })
+        yield* Effect.logWarning("reply for unknown request", { requestID: input.requestID })
         return yield* new NotFoundError({ requestID: input.requestID })
       }
       pending.delete(input.requestID)
-      log.info("replied", { requestID: input.requestID, answers: input.answers })
+      yield* Effect.logInfo("replied", { requestID: input.requestID, answers: input.answers })
       yield* events.publish(Event.Replied, {
         sessionID: existing.info.sessionID,
         requestID: existing.info.id,
@@ -203,11 +200,11 @@ export const layer = Layer.effect(
       const pending = (yield* InstanceState.get(state)).pending
       const existing = pending.get(requestID)
       if (!existing) {
-        log.warn("reject for unknown request", { requestID })
+        yield* Effect.logWarning("reject for unknown request", { requestID })
         return yield* new NotFoundError({ requestID })
       }
       pending.delete(requestID)
-      log.info("rejected", { requestID })
+      yield* Effect.logInfo("rejected", { requestID })
       yield* events.publish(Event.Rejected, {
         sessionID: existing.info.sessionID,
         requestID: existing.info.id,

+ 1 - 3
packages/opencode/src/reference/reference.ts

@@ -104,9 +104,7 @@ function materializeReference(cache: RepositoryCache.Interface, reference: Extra
   return cache.ensure({ reference: reference.reference, branch: reference.branch, refresh: true }).pipe(
     Effect.asVoid,
     Effect.catchCause((cause) =>
-      Effect.logWarning("failed to materialize reference repository").pipe(
-        Effect.annotateLogs({ name: reference.name, cause }),
-      ),
+      Effect.logWarning("failed to materialize reference repository", { name: reference.name, cause }),
     ),
   )
 }

+ 1 - 10
packages/opencode/src/server/global-lifecycle.ts

@@ -1,11 +1,8 @@
 import { GlobalBus } from "@/bus/global"
 import { InstanceStore } from "@/project/instance-store"
-import * as Log from "@opencode-ai/core/util/log"
 import { Effect } from "effect"
 import { Event } from "./event"
 
-const log = Log.create({ service: "server" })
-
 export const emitGlobalDisposed = Effect.sync(() =>
   GlobalBus.emit("event", {
     directory: "global",
@@ -21,13 +18,7 @@ export const disposeAllInstancesAndEmitGlobalDisposed = Effect.fn("Server.dispos
     const store = yield* InstanceStore.Service
     yield* Effect.gen(function* () {
       yield* options?.swallowErrors
-        ? store.disposeAll().pipe(
-            Effect.catchCause((cause) =>
-              Effect.sync(() => {
-                log.warn("global disposal failed", { cause })
-              }),
-            ),
-          )
+        ? store.disposeAll().pipe(Effect.catchCause((cause) => Effect.logWarning("global disposal failed", { cause })))
         : store.disposeAll()
       yield* emitGlobalDisposed
     }).pipe(Effect.uninterruptible)

+ 3 - 16
packages/opencode/src/server/mdns.ts

@@ -1,8 +1,5 @@
-import * as Log from "@opencode-ai/core/util/log"
 import { Bonjour } from "bonjour-service"
 
-const log = Log.create({ service: "mdns" })
-
 let bonjour: Bonjour | undefined
 let currentPort: number | undefined
 
@@ -22,17 +19,10 @@ export function publish(port: number, domain?: string) {
       txt: { path: "/" },
     })
 
-    service.on("up", () => {
-      log.info("mDNS service published", { name, port })
-    })
-
-    service.on("error", (err) => {
-      log.error("mDNS service error", { error: err })
-    })
+    service.on("error", () => {})
 
     currentPort = port
-  } catch (err) {
-    log.error("mDNS publish failed", { error: err })
+  } catch {
     if (bonjour) {
       try {
         bonjour.destroy()
@@ -48,12 +38,9 @@ export function unpublish() {
     try {
       bonjour.unpublishAll()
       bonjour.destroy()
-    } catch (err) {
-      log.error("mDNS unpublish failed", { error: err })
-    }
+    } catch {}
     bonjour = undefined
     currentPort = undefined
-    log.info("mDNS service unpublished")
   }
 }
 

+ 9 - 3
packages/opencode/src/server/routes/instance/httpapi/handlers/control.ts

@@ -1,6 +1,5 @@
 import { Auth } from "@/auth"
 
-import * as Log from "@opencode-ai/core/util/log"
 import { Effect } from "effect"
 import { HttpApiBuilder } from "effect/unstable/httpapi"
 import { RootHttpApi } from "../api"
@@ -27,8 +26,15 @@ export const controlHandlers = HttpApiBuilder.group(RootHttpApi, "control", (han
     })
 
     const log = Effect.fn("ControlHttpApi.log")(function* (ctx: { payload: typeof LogInput.Type }) {
-      const logger = Log.create({ service: ctx.payload.service })
-      logger[ctx.payload.level](ctx.payload.message, ctx.payload.extra)
+      const write =
+        ctx.payload.level === "debug"
+          ? Effect.logDebug
+          : ctx.payload.level === "info"
+            ? Effect.logInfo
+            : ctx.payload.level === "warn"
+              ? Effect.logWarning
+              : Effect.logError
+      yield* write(ctx.payload.message).pipe(Effect.annotateLogs(ctx.payload.extra ?? {}))
       return true
     })
 

+ 2 - 5
packages/opencode/src/server/routes/instance/httpapi/handlers/event.ts

@@ -2,7 +2,6 @@ import { EventV2Bridge } from "@/event-v2-bridge"
 import { InstanceState } from "@/effect/instance-state"
 import { GlobalBus } from "@/bus/global"
 import { EventV2 } from "@opencode-ai/core/event"
-import * as Log from "@opencode-ai/core/util/log"
 import { Effect, Queue } from "effect"
 import * as Stream from "effect/Stream"
 import { HttpServerResponse } from "effect/unstable/http"
@@ -10,8 +9,6 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"
 import * as Sse from "effect/unstable/encoding/Sse"
 import { EventApi } from "../groups/event"
 
-const log = Log.create({ service: "server" })
-
 function eventData(data: unknown): Sse.Event {
   return {
     _tag: "Event",
@@ -68,14 +65,14 @@ function eventResponse(events: EventV2.Interface) {
       Stream.map(() => ({ id: eventID(), type: "server.heartbeat", properties: {} })),
     )
 
-    log.info("event connected")
+    yield* Effect.logInfo("event connected")
     return HttpServerResponse.stream(
       Stream.make({ id: eventID(), type: "server.connected", properties: {} }).pipe(
         Stream.concat(output.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
         Stream.map(eventData),
         Stream.pipeThroughChannel(Sse.encode()),
         Stream.encodeText,
-        Stream.ensuring(Effect.sync(() => log.info("event disconnected"))),
+        Stream.ensuring(Effect.logInfo("event disconnected")),
       ),
       {
         contentType: "text/event-stream",

+ 2 - 5
packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts

@@ -4,15 +4,12 @@ import { LocationServiceMap } from "@opencode-ai/core/location-layer"
 import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
 import { Search } from "@opencode-ai/core/filesystem/search"
 import { FSUtil } from "@opencode-ai/core/fs-util"
-import { Log } from "@opencode-ai/core/util/log"
 import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
 import { Effect, Layer } from "effect"
 import path from "path"
 import { HttpApiBuilder } from "effect/unstable/httpapi"
 import { InstanceHttpApi } from "../api"
 
-const log = Log.create({ service: "server.file" })
-
 export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handlers) =>
   Effect.gen(function* () {
     const ripgrep = yield* Ripgrep.Service
@@ -42,7 +39,7 @@ export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handl
       // to the ripgrep-backed FileSystem.find when fff is unavailable.
       const fff = yield* search.file({ cwd: directory, query: ctx.query.query, limit, kind }).pipe(Effect.orDie)
       if (fff !== undefined) {
-        log.info("find file", {
+        yield* Effect.logInfo("find file", {
           engine: "fff",
           query: ctx.query.query,
           kind,
@@ -62,7 +59,7 @@ export const fileHandlers = HttpApiBuilder.group(InstanceHttpApi, "file", (handl
           }),
         ),
       )).map((item) => item.path)
-      log.info("find file", {
+      yield* Effect.logInfo("find file", {
         engine: "ripgrep",
         query: ctx.query.query,
         kind,

+ 30 - 31
packages/opencode/src/server/routes/instance/httpapi/handlers/global.ts

@@ -5,7 +5,6 @@ import { EventV2 } from "@opencode-ai/core/event"
 import { Installation } from "@/installation"
 import { disposeAllInstancesAndEmitGlobalDisposed } from "@/server/global-lifecycle"
 import { InstallationVersion } from "@opencode-ai/core/installation/version"
-import * as Log from "@opencode-ai/core/util/log"
 import { Effect, Queue, Schema } from "effect"
 import * as Stream from "effect/Stream"
 import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
@@ -14,8 +13,6 @@ import * as Sse from "effect/unstable/encoding/Sse"
 import { RootHttpApi } from "../api"
 import { GlobalUpgradeInput } from "../groups/global"
 
-const log = Log.create({ service: "server" })
-
 function eventData(data: unknown): Sse.Event {
   return {
     _tag: "Event",
@@ -34,36 +31,38 @@ function parseBody(body: string) {
 }
 
 function eventResponse() {
-  log.info("global event connected")
-  const events = Stream.callback<GlobalBusEvent>((queue) => {
-    const handler = (event: GlobalBusEvent) => Queue.offerUnsafe(queue, event)
-    return Effect.acquireRelease(
-      Effect.sync(() => GlobalBus.on("event", handler)),
-      () => Effect.sync(() => GlobalBus.off("event", handler)),
+  return Effect.gen(function* () {
+    yield* Effect.logInfo("global event connected")
+    const events = Stream.callback<GlobalBusEvent>((queue) => {
+      const handler = (event: GlobalBusEvent) => Queue.offerUnsafe(queue, event)
+      return Effect.acquireRelease(
+        Effect.sync(() => GlobalBus.on("event", handler)),
+        () => Effect.sync(() => GlobalBus.off("event", handler)),
+      )
+    })
+    const heartbeat = Stream.tick("10 seconds").pipe(
+      Stream.drop(1),
+      Stream.map(() => ({ payload: { id: EventV2.ID.create(), type: "server.heartbeat", properties: {} } })),
     )
-  })
-  const heartbeat = Stream.tick("10 seconds").pipe(
-    Stream.drop(1),
-    Stream.map(() => ({ payload: { id: EventV2.ID.create(), type: "server.heartbeat", properties: {} } })),
-  )
 
-  return HttpServerResponse.stream(
-    Stream.make({ payload: { id: EventV2.ID.create(), type: "server.connected", properties: {} } }).pipe(
-      Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
-      Stream.map(eventData),
-      Stream.pipeThroughChannel(Sse.encode()),
-      Stream.encodeText,
-      Stream.ensuring(Effect.sync(() => log.info("global event disconnected"))),
-    ),
-    {
-      contentType: "text/event-stream",
-      headers: {
-        "Cache-Control": "no-cache, no-transform",
-        "X-Accel-Buffering": "no",
-        "X-Content-Type-Options": "nosniff",
+    return HttpServerResponse.stream(
+      Stream.make({ payload: { id: EventV2.ID.create(), type: "server.connected", properties: {} } }).pipe(
+        Stream.concat(events.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }))),
+        Stream.map(eventData),
+        Stream.pipeThroughChannel(Sse.encode()),
+        Stream.encodeText,
+        Stream.ensuring(Effect.logInfo("global event disconnected")),
+      ),
+      {
+        contentType: "text/event-stream",
+        headers: {
+          "Cache-Control": "no-cache, no-transform",
+          "X-Accel-Buffering": "no",
+          "X-Content-Type-Options": "nosniff",
+        },
       },
-    },
-  )
+    )
+  })
 }
 
 export const globalHandlers = HttpApiBuilder.group(RootHttpApi, "global", (handlers) =>
@@ -77,7 +76,7 @@ export const globalHandlers = HttpApiBuilder.group(RootHttpApi, "global", (handl
     })
 
     const event = Effect.fn("GlobalHttpApi.event")(function* () {
-      return eventResponse()
+      return yield* eventResponse()
     })
 
     const configGet = Effect.fn("GlobalHttpApi.configGet")(function* () {

+ 1 - 3
packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts

@@ -314,9 +314,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
       yield* promptSvc.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID }).pipe(
         Effect.catchCause((cause) =>
           Effect.gen(function* () {
-            yield* Effect.logError("prompt_async failed").pipe(
-              Effect.annotateLogs({ sessionID: ctx.params.sessionID, cause }),
-            )
+            yield* Effect.logError("prompt_async failed", { sessionID: ctx.params.sessionID, cause })
             yield* events.publish(Session.Event.Error, {
               sessionID: ctx.params.sessionID,
               error: new NamedError.Unknown({ message: Cause.pretty(cause) }).toObject(),

+ 3 - 9
packages/opencode/src/server/routes/instance/httpapi/handlers/sync.ts

@@ -15,9 +15,6 @@ import { Effect, Scope } from "effect"
 import { HttpApiBuilder, HttpApiError } from "effect/unstable/httpapi"
 import { InstanceHttpApi } from "../api"
 import { HistoryPayload, ReplayPayload, SessionPayload } from "../groups/sync"
-import * as Log from "@opencode-ai/core/util/log"
-
-const log = Log.create({ service: "server.sync" })
 
 export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handlers) =>
   Effect.gen(function* () {
@@ -43,7 +40,7 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
         data: { ...event.data },
       }))
       const source = payload[0].aggregateID
-      log.info("sync replay requested", {
+      yield* Effect.logInfo("sync replay requested", {
         sessionID: source,
         events: payload.length,
         first: payload[0]?.seq,
@@ -52,7 +49,7 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
       })
       const ownerID = yield* InstanceState.workspaceID
       yield* events.replayAll(payload, { ownerID, strictOwner: true })
-      log.info("sync replay complete", {
+      yield* Effect.logInfo("sync replay complete", {
         sessionID: source,
         events: payload.length,
         first: payload[0]?.seq,
@@ -67,10 +64,7 @@ export const syncHandlers = HttpApiBuilder.group(InstanceHttpApi, "sync", (handl
 
       yield* session.setWorkspace({ sessionID: ctx.payload.sessionID, workspaceID })
 
-      log.info("sync session stolen", {
-        sessionID: ctx.payload.sessionID,
-        workspaceID,
-      })
+      yield* Effect.logInfo("sync session stolen", { sessionID: ctx.payload.sessionID, workspaceID })
 
       return { sessionID: ctx.payload.sessionID }
     })

+ 1 - 4
packages/opencode/src/server/routes/instance/httpapi/lifecycle.ts

@@ -1,12 +1,9 @@
 import { EffectBridge } from "@/effect/bridge"
 import type { InstanceContext } from "@/project/instance-context"
 import { InstanceStore } from "@/project/instance-store"
-import * as Log from "@opencode-ai/core/util/log"
 import { Effect } from "effect"
 import { HttpEffect, HttpMiddleware, HttpServerRequest } from "effect/unstable/http"
 
-const log = Log.create({ service: "server" })
-
 type MarkedInstance = {
   ctx: InstanceContext
   store: InstanceStore.Interface
@@ -51,7 +48,7 @@ export const disposeMiddleware: HttpMiddleware.HttpMiddleware = (effect) =>
     if (!marked) return response
     disposeAfterResponse.delete(request.source)
     yield* Effect.uninterruptible(marked.bridge.run(marked.store.dispose(marked.ctx))).pipe(
-      Effect.catchCause((cause) => Effect.sync(() => log.warn("instance disposal failed", { cause }))),
+      Effect.catchCause((cause) => Effect.logWarning("instance disposal failed", { cause })),
     )
     return response
   })

+ 9 - 12
packages/opencode/src/server/routes/instance/httpapi/middleware/error.ts

@@ -1,10 +1,7 @@
 import { NamedError } from "@opencode-ai/core/util/error"
-import * as Log from "@opencode-ai/core/util/log"
 import { Cause, Effect } from "effect"
 import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"
 
-const log = Log.create({ service: "server" })
-
 // Keep typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s.
 export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
   effect.pipe(
@@ -20,15 +17,15 @@ export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect)
       const error = defect.defect
       const ref = `err_${crypto.randomUUID().slice(0, 8)}`
 
-      log.error("failed", { ref, error, cause: Cause.pretty(cause) })
-
-      return Effect.succeed(
-        HttpServerResponse.jsonUnsafe(
-          new NamedError.Unknown({
-            message: "Unexpected server error. Check server logs for details.",
-            ref,
-          }).toObject(),
-          { status: 500 },
+      return Effect.logError("failed", { ref, error, cause: Cause.pretty(cause) }).pipe(
+        Effect.as(
+          HttpServerResponse.jsonUnsafe(
+            new NamedError.Unknown({
+              message: "Unexpected server error. Check server logs for details.",
+              ref,
+            }).toObject(),
+            { status: 500 },
+          ),
         ),
       )
     }),

+ 14 - 15
packages/opencode/src/server/routes/instance/httpapi/middleware/schema-error.ts

@@ -1,11 +1,8 @@
 import { Effect } from "effect"
 import { HttpServerResponse } from "effect/unstable/http"
 import { HttpApiMiddleware } from "effect/unstable/httpapi"
-import * as Log from "@opencode-ai/core/util/log"
 import { InvalidRequestError } from "../errors"
 
-const log = Log.create({ service: "server" })
-
 // Effect's Issue formatter recursively dumps the rejected `actual` value with
 // no truncation, so a 5KB invalid array produces a ~360KB string. Cap to keep
 // 4xx responses small and avoid mirroring entire request payloads (which may
@@ -27,16 +24,18 @@ export class SchemaErrorMiddleware extends HttpApiMiddleware.Service<SchemaError
 
 export const schemaErrorLayer = HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrorMiddleware, (error, context) => {
   const reason = truncateReason(error.cause.message)
-  log.warn("schema rejection", { kind: error.kind, reason })
-  if (context.endpoint.path.startsWith("/api/")) {
-    return Effect.fail(
-      new InvalidRequestError({
-        message: reason,
-        kind: error.kind,
-      }),
-    )
-  }
-  return Effect.succeed(
-    HttpServerResponse.jsonUnsafe({ name: "BadRequest", data: { message: reason, kind: error.kind } }, { status: 400 }),
-  )
+  const response = context.endpoint.path.startsWith("/api/")
+    ? Effect.fail(
+        new InvalidRequestError({
+          message: reason,
+          kind: error.kind,
+        }),
+      )
+    : Effect.succeed(
+        HttpServerResponse.jsonUnsafe(
+          { name: "BadRequest", data: { message: reason, kind: error.kind } },
+          { status: 400 },
+        ),
+      )
+  return Effect.logWarning("schema rejection", { kind: error.kind, reason }).pipe(Effect.andThen(response))
 })

+ 1 - 1
packages/opencode/src/server/routes/instance/httpapi/server.ts

@@ -16,7 +16,7 @@ import { Auth } from "@/auth"
 import { BackgroundJob } from "@/background/job"
 import { Config } from "@/config/config"
 import { Command } from "@/command"
-import * as Observability from "@opencode-ai/core/effect/observability"
+import * as Observability from "@opencode-ai/core/observability"
 import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
 import { Format } from "@/format"
 import { RuntimeFlags } from "@/effect/runtime-flags"

+ 3 - 4
packages/opencode/src/server/server.ts

@@ -1,7 +1,6 @@
 import "./init-projectors"
 
 import { NodeHttpServer } from "@effect/platform-node"
-import * as Log from "@opencode-ai/core/util/log"
 import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect"
 import { HttpRouter, HttpServer } from "effect/unstable/http"
 import { OpenApi } from "effect/unstable/httpapi"
@@ -17,8 +16,6 @@ import { lazy } from "@/util/lazy"
 // @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
 globalThis.AI_SDK_LOG_WARNINGS = false
 
-const log = Log.create({ service: "server" })
-
 export type Listener = {
   hostname: string
   port: number
@@ -165,7 +162,9 @@ function setupMdns(opts: ListenOptions, port: number, scope: Scope.Scope) {
       yield* Scope.addFinalizer(scope, unpublish)
       return unpublish
     }
-    if (opts.mdns) log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish")
+    if (opts.mdns) {
+      yield* Effect.logWarning("mDNS enabled but hostname is loopback; skipping mDNS publish")
+    }
     return Effect.void
   })
 }

+ 2 - 10
packages/opencode/src/server/shared/fence.ts

@@ -3,12 +3,10 @@ import { inArray } from "drizzle-orm"
 import { EventSequenceTable } from "@opencode-ai/core/event/sql"
 import { Workspace } from "@/control-plane/workspace"
 import type { WorkspaceV2 } from "@opencode-ai/core/workspace"
-import * as Log from "@opencode-ai/core/util/log"
 import { Effect } from "effect"
 
 export const HEADER = "x-opencode-sync"
 export type State = Record<string, number>
-const log = Log.create({ service: "fence" })
 
 export function load(db: Database.Interface["db"], ids?: string[]) {
   return Effect.gen(function* () {
@@ -55,14 +53,8 @@ export function parse(headers: Headers): State | undefined {
 
 export function wait(workspaceID: WorkspaceV2.ID, state: State, signal?: AbortSignal) {
   return Effect.gen(function* () {
-    log.info("waiting for state", {
-      workspaceID,
-      state,
-    })
+    yield* Effect.logInfo("waiting for state", { workspaceID, state })
     yield* Workspace.Service.use((workspace) => workspace.waitForSync(workspaceID, state, signal))
-    log.info("state fully synced", {
-      workspaceID,
-      state,
-    })
+    yield* Effect.logInfo("state fully synced", { workspaceID, state })
   })
 }

+ 6 - 7
packages/opencode/src/session/compaction.ts

@@ -5,7 +5,6 @@ import { SessionID, MessageID, PartID } from "./schema"
 import { Provider } from "@/provider/provider"
 import { MessageV2 } from "./message-v2"
 import { Token } from "@/util/token"
-import { Log } from "@opencode-ai/core/util/log"
 import { SessionProcessor } from "./processor"
 import { Agent } from "@/agent/agent"
 import { Plugin } from "@/plugin"
@@ -26,8 +25,6 @@ import { ModelV2 } from "@opencode-ai/core/model"
 import { EventV2 } from "@opencode-ai/core/event"
 import { buildPrompt } from "@opencode-ai/core/session/compaction"
 
-const log = Log.create({ service: "session.compaction" })
-
 export const Event = {
   Compacted: EventV2.define({
     type: "session.compacted",
@@ -237,7 +234,9 @@ export const layer = Layer.effect(
           estimate,
         })
         if (split) keep = split
-        else if (!keep) log.info("tail fallback", { budget, size, total })
+        else if (!keep) {
+          yield* Effect.logInfo("tail fallback", { budget, size, total })
+        }
         break
       }
 
@@ -253,7 +252,7 @@ export const layer = Layer.effect(
     const prune = Effect.fn("SessionCompaction.prune")(function* (input: { sessionID: SessionID }) {
       const cfg = yield* config.get()
       if (!cfg.compaction?.prune) return
-      log.info("pruning")
+      yield* Effect.logInfo("pruning")
 
       const msgs = yield* session
         .messages({ sessionID: input.sessionID })
@@ -284,7 +283,7 @@ export const layer = Layer.effect(
         }
       }
 
-      log.info("found", { pruned, total })
+      yield* Effect.logInfo("found", { pruned, total })
       if (pruned > PRUNE_MINIMUM) {
         for (const part of toPrune) {
           if (part.state.status === "completed") {
@@ -292,7 +291,7 @@ export const layer = Layer.effect(
             yield* session.updatePart(part)
           }
         }
-        log.info("pruned", { count: toPrune.length })
+        yield* Effect.logInfo("pruned", { count: toPrune.length })
       }
     })
 

+ 30 - 43
packages/opencode/src/session/llm.ts

@@ -2,7 +2,6 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission"
 import { Provider } from "@/provider/provider"
 import { SessionV1 } from "@opencode-ai/core/v1/session"
 import { serviceUse } from "@opencode-ai/core/effect/service-use"
-import { Log } from "@opencode-ai/core/util/log"
 import { Context, Effect, Layer } from "effect"
 import * as Stream from "effect/Stream"
 import { streamText, wrapLanguageModel, type ModelMessage, type Tool } from "ai"
@@ -29,7 +28,6 @@ import { LLMAISDK } from "./llm/ai-sdk"
 import { LLMNativeRuntime } from "./llm/native-runtime"
 import { LLMRequestPrep } from "./llm/request"
 
-const log = Log.create({ service: "llm" })
 export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
 
 export type StreamInput = {
@@ -83,17 +81,13 @@ const live: Layer.Layer<
     const flags = yield* RuntimeFlags.Service
 
     const run = Effect.fn("LLM.run")(function* (input: StreamRequest) {
-      const l = log
-        .clone()
-        .tag("providerID", input.model.providerID)
-        .tag("modelID", input.model.id)
-        .tag("session.id", input.sessionID)
-        .tag("small", (input.small ?? false).toString())
-        .tag("agent", input.agent.name)
-        .tag("mode", input.agent.mode)
-      l.info("stream", {
-        modelID: input.model.id,
+      yield* Effect.logInfo("stream", {
         providerID: input.model.providerID,
+        modelID: input.model.id,
+        "session.id": input.sessionID,
+        small: (input.small ?? false).toString(),
+        agent: input.agent.name,
+        mode: input.agent.mode,
       })
 
       const [language, cfg, item, info] = yield* Effect.all(
@@ -245,36 +239,38 @@ const live: Layer.Layer<
           abort: input.abort,
         })
         if (native.type === "supported") {
-          yield* Effect.logInfo("llm runtime selected").pipe(
-            Effect.annotateLogs({
-              "llm.runtime": "native",
-              "llm.provider": input.model.providerID,
-              "llm.model": input.model.id,
-            }),
-          )
+          yield* Effect.logInfo("llm runtime selected", {
+            "llm.runtime": "native",
+            "llm.provider": input.model.providerID,
+            "llm.model": input.model.id,
+          })
           return {
             type: "native" as const,
             stream: native.stream,
           }
         }
-        yield* Effect.logInfo("llm runtime selected").pipe(
-          Effect.annotateLogs({
-            "llm.runtime": "ai-sdk",
-            "llm.provider": input.model.providerID,
-            "llm.model": input.model.id,
-            "llm.native_unsupported_reason": native.reason,
-          }),
-        )
-        l.info("native runtime unavailable; falling back to ai-sdk", { reason: native.reason })
-      }
-
-      yield* Effect.logInfo("llm runtime selected").pipe(
-        Effect.annotateLogs({
+        yield* Effect.logInfo("llm runtime selected", {
           "llm.runtime": "ai-sdk",
           "llm.provider": input.model.providerID,
           "llm.model": input.model.id,
-        }),
-      )
+          "llm.native_unsupported_reason": native.reason,
+        })
+        yield* Effect.logInfo("native runtime unavailable; falling back to ai-sdk", {
+          providerID: input.model.providerID,
+          modelID: input.model.id,
+          "session.id": input.sessionID,
+          small: (input.small ?? false).toString(),
+          agent: input.agent.name,
+          mode: input.agent.mode,
+          reason: native.reason,
+        })
+      }
+
+      yield* Effect.logInfo("llm runtime selected", {
+        "llm.runtime": "ai-sdk",
+        "llm.provider": input.model.providerID,
+        "llm.model": input.model.id,
+      })
       // Default runtime path: AI SDK owns provider execution and tool dispatch;
       // LLMAISDK.toLLMEvents below normalizes fullStream parts for the processor.
       return {
@@ -282,18 +278,9 @@ const live: Layer.Layer<
         result: streamText({
           // Copilot returns the authoritative billed amount only in provider-specific response fields.
           includeRawChunks: input.model.providerID.includes("github-copilot"),
-          onError(error) {
-            l.error("stream error", {
-              error,
-            })
-          },
           async experimental_repairToolCall(failed) {
             const lower = failed.toolCall.toolName.toLowerCase()
             if (lower !== failed.toolCall.toolName && prepared.tools[lower]) {
-              l.info("repairing tool call", {
-                tool: failed.toolCall.toolName,
-                repaired: lower,
-              })
               return {
                 ...failed.toolCall,
                 toolName: lower,

+ 1 - 2
packages/opencode/src/session/message-v2.ts

@@ -37,7 +37,6 @@ import { isMedia } from "@/util/media"
 import type { SystemError } from "bun"
 import type { Provider } from "@/provider/provider"
 import { Effect, Schema } from "effect"
-import * as EffectLogger from "@opencode-ai/core/effect/logger"
 
 /** Error shape thrown by Bun's fetch() when gzip/br decompression fails mid-stream */
 interface FetchDecompressionError extends Error {
@@ -431,7 +430,7 @@ export function toModelMessages(
   model: Provider.Model,
   options?: { stripMedia?: boolean; toolOutputMaxChars?: number },
 ): Promise<ModelMessage[]> {
-  return Effect.runPromise(toModelMessagesEffect(input, model, options).pipe(Effect.provide(EffectLogger.layer)))
+  return Effect.runPromise(toModelMessagesEffect(input, model, options))
 }
 
 export const page = Effect.fn("MessageV2.page")(function* (input: {

+ 10 - 6
packages/opencode/src/session/processor.ts

@@ -20,7 +20,6 @@ import { SessionSummary } from "./summary"
 import type { Provider } from "@/provider/provider"
 import { Question } from "@/question"
 import { errorMessage } from "@/util/error"
-import { Log } from "@opencode-ai/core/util/log"
 import { isRecord } from "@/util/record"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { Database } from "@opencode-ai/core/database/database"
@@ -34,8 +33,6 @@ import { toolFileSourceFromUri, Usage, type LLMEvent } from "@opencode-ai/llm"
 import { ToolOutput } from "@opencode-ai/core/tool-output"
 
 const DOOM_LOOP_THRESHOLD = 3
-const log = Log.create({ service: "session.processor" })
-
 export type Result = "compact" | "stop" | "continue"
 
 export interface Handle {
@@ -131,7 +128,6 @@ export const layer = Layer.effect(
       }
       const mirrorAssistant = flags.experimentalEventSystem && !input.assistantMessage.summary
       let aborted = false
-      const slog = log.clone().tag("session.id", input.sessionID).tag("messageID", input.assistantMessage.id)
 
       const parse = (e: unknown) =>
         MessageV2.fromError(e, {
@@ -918,7 +914,12 @@ export const layer = Layer.effect(
       })
 
       const halt = Effect.fn("SessionProcessor.halt")(function* (e: unknown) {
-        slog.error("process", { error: errorMessage(e), stack: e instanceof Error ? e.stack : undefined })
+        yield* Effect.logError("process", {
+          "session.id": input.sessionID,
+          messageID: input.assistantMessage.id,
+          error: errorMessage(e),
+          stack: e instanceof Error ? e.stack : undefined,
+        })
         const error = parse(e)
         yield* flushV2Fragments()
         if (SessionV1.ContextOverflowError.isInstance(error)) {
@@ -956,7 +957,10 @@ export const layer = Layer.effect(
       })
 
       const process = Effect.fn("SessionProcessor.process")(function* (streamInput: LLM.StreamInput) {
-        slog.info("process")
+        yield* Effect.logInfo("process", {
+          "session.id": input.sessionID,
+          messageID: input.assistantMessage.id,
+        })
         ctx.needsCompaction = false
         ctx.shouldBreak = (yield* config.get()).experimental?.continue_loop_on_deny !== true
 

+ 26 - 24
packages/opencode/src/session/prompt.ts

@@ -4,7 +4,6 @@ import { SessionV1 } from "@opencode-ai/core/v1/session"
 import os from "os"
 import { SessionID, MessageID, PartID } from "./schema"
 import { MessageV2 } from "./message-v2"
-import { Log } from "@opencode-ai/core/util/log"
 import { SessionRevert } from "./revert"
 import { Session } from "./session"
 import { Agent } from "../agent/agent"
@@ -43,7 +42,6 @@ import { Image } from "@/image/image"
 import { decodeDataUrl } from "@/util/data-url"
 import { Process } from "@/util/process"
 import { Cause, Effect, Exit, Latch, Layer, Option, Scope, Context, Schema, Types } from "effect"
-import * as EffectLogger from "@opencode-ai/core/effect/logger"
 import { InstanceState } from "@/effect/instance-state"
 import { TaskTool, type TaskPromptOps } from "@/tool/task"
 import { SessionRunState } from "./run-state"
@@ -80,9 +78,6 @@ IMPORTANT:
 
 const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.`
 
-const log = Log.create({ service: "session.prompt" })
-const elog = EffectLogger.create({ service: "session.prompt" })
-
 function isOrphanedInterruptedTool(part: SessionV1.ToolPart) {
   // cleanup() marks abandoned tool_use blocks this way after retries/aborts.
   // They are not pending work and must not trigger an assistant-prefill request.
@@ -141,7 +136,7 @@ export const layer = Layer.effect(
     })
 
     const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) {
-      yield* elog.info("cancel", { sessionID })
+      yield* Effect.logInfo("cancel", { "session.id": sessionID })
       yield* state.cancel(sessionID)
     })
 
@@ -282,7 +277,7 @@ export const layer = Layer.effect(
       const t = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
       yield* sessions
         .setTitle({ sessionID: input.session.id, title: t })
-        .pipe(Effect.catchCause((cause) => elog.error("failed to generate title", { error: Cause.squash(cause) })))
+        .pipe(Effect.catchCause((cause) => Effect.logError("failed to generate title", { error: Cause.squash(cause) })))
     })
 
     const handleSubtask = Effect.fn("SessionPrompt.handleSubtask")(function* (input: {
@@ -384,8 +379,11 @@ export const layer = Layer.effect(
           Effect.catchCause((cause) => {
             const defect = Cause.squash(cause)
             error = defect instanceof Error ? defect : new Error(String(defect))
-            log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
-            return Effect.void
+            return Effect.logError("subtask execution failed", {
+              error,
+              agent: task.agent,
+              description: task.description,
+            })
           }),
           Effect.onInterrupt(() =>
             Effect.gen(function* () {
@@ -761,7 +759,7 @@ export const layer = Layer.effect(
         if (part.type === "file") {
           if (part.source?.type === "resource") {
             const { clientName, uri } = part.source
-            log.info("mcp resource", { clientName, uri, mime: part.mime })
+            yield* Effect.logInfo("mcp resource", { clientName, uri, mime: part.mime })
             const pieces: Draft<SessionV1.Part>[] = [
               {
                 messageID: info.id,
@@ -799,7 +797,7 @@ export const layer = Layer.effect(
               pieces.push({ ...part, messageID: info.id, sessionID: input.sessionID })
             } else {
               const error = Cause.squash(exit.cause)
-              log.error("failed to read MCP resource", { error, clientName, uri })
+              yield* Effect.logError("failed to read MCP resource", { error, clientName, uri })
               const message = error instanceof Error ? error.message : String(error)
               pieces.push({
                 messageID: info.id,
@@ -835,7 +833,7 @@ export const layer = Layer.effect(
               }
               break
             case "file:": {
-              log.info("file", { mime: part.mime })
+              yield* Effect.logInfo("file", { mime: part.mime })
               const filepath = fileURLToPath(part.url)
               const mime = (yield* fsys.isDir(filepath)) ? "application/x-directory" : part.mime
 
@@ -918,7 +916,7 @@ export const layer = Layer.effect(
                   }
                 } else {
                   const error = Cause.squash(exit.cause)
-                  log.error("failed to read file", { error })
+                  yield* Effect.logError("failed to read file", { error, filepath })
                   const message = error instanceof Error ? error.message : String(error)
                   yield* events.publish(Session.Event.Error, {
                     sessionID: input.sessionID,
@@ -940,7 +938,7 @@ export const layer = Layer.effect(
                 const exit = yield* execRead(args).pipe(Effect.exit)
                 if (Exit.isFailure(exit)) {
                   const error = Cause.squash(exit.cause)
-                  log.error("failed to read directory", { error })
+                  yield* Effect.logError("failed to read directory", { error, filepath })
                   const message = error instanceof Error ? error.message : String(error)
                   yield* events.publish(Session.Event.Error, {
                     sessionID: input.sessionID,
@@ -1065,7 +1063,7 @@ export const layer = Layer.effect(
 
       const parsed = decodeMessageInfo(info, { errors: "all", propertyOrder: "original" })
       if (Exit.isFailure(parsed)) {
-        log.error("invalid user message before save", {
+        yield* Effect.logError("invalid user message before save", {
           sessionID: input.sessionID,
           messageID: info.id,
           agent: info.agent,
@@ -1073,10 +1071,10 @@ export const layer = Layer.effect(
           cause: Cause.pretty(parsed.cause),
         })
       }
-      parts.forEach((part, index) => {
+      for (const [index, part] of parts.entries()) {
         const p = decodeMessagePart(part, { errors: "all", propertyOrder: "original" })
-        if (Exit.isSuccess(p)) return
-        log.error("invalid user part before save", {
+        if (Exit.isSuccess(p)) continue
+        yield* Effect.logError("invalid user part before save", {
           sessionID: input.sessionID,
           messageID: info.id,
           partID: part.id,
@@ -1085,7 +1083,7 @@ export const layer = Layer.effect(
           cause: Cause.pretty(p.cause),
           part,
         })
-      })
+      }
 
       yield* sessions.updateMessage(info)
       for (const part of parts) yield* sessions.updatePart(part)
@@ -1217,14 +1215,13 @@ export const layer = Layer.effect(
     const runLoop: (sessionID: SessionID) => Effect.Effect<SessionV1.WithParts> = Effect.fn("SessionPrompt.run")(
       function* (sessionID: SessionID) {
         const ctx = yield* InstanceState.context
-        const slog = elog.with({ sessionID })
         let structured: unknown
         let step = 0
         const session = yield* sessions.get(sessionID).pipe(Effect.orDie)
 
         while (true) {
           yield* status.set(sessionID, { type: "busy" })
-          yield* slog.info("loop", { step })
+          yield* Effect.logInfo("loop", { "session.id": sessionID, step })
 
           let msgs = yield* MessageV2.filterCompactedEffect(sessionID).pipe(
             Effect.provideService(Database.Service, database),
@@ -1255,13 +1252,14 @@ export const layer = Layer.effect(
               (part): part is SessionV1.ToolPart => part.type === "tool" && isOrphanedInterruptedTool(part),
             )
             if (orphan) {
-              yield* slog.warn("loop exit with orphaned interrupted tool", {
+              yield* Effect.logWarning("loop exit with orphaned interrupted tool", {
+                "session.id": sessionID,
                 messageID: lastAssistant.id,
                 tool: orphan.tool,
                 callID: orphan.callID,
               })
             }
-            yield* slog.info("exiting loop")
+            yield* Effect.logInfo("exiting loop", { "session.id": sessionID })
             break
           }
 
@@ -1486,7 +1484,11 @@ export const layer = Layer.effect(
     })
 
     const command = Effect.fn("SessionPrompt.command")(function* (input: CommandInput) {
-      yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent })
+      yield* Effect.logInfo("command", {
+        "session.id": input.sessionID,
+        command: input.command,
+        agent: input.agent,
+      })
       const cmd = yield* commands.get(input.command)
       if (!cmd) {
         const available = (yield* commands.list()).map((c) => c.name)

+ 1 - 4
packages/opencode/src/session/revert.ts

@@ -3,15 +3,12 @@ import { SessionV1 } from "@opencode-ai/core/v1/session"
 import { EventV2Bridge } from "@/event-v2-bridge"
 import { Snapshot } from "../snapshot"
 import { Storage } from "@/storage/storage"
-import { Log } from "@opencode-ai/core/util/log"
 import { Session } from "./session"
 import { MessageV2 } from "./message-v2"
 import { SessionID, MessageID, PartID } from "./schema"
 import { SessionRunState } from "./run-state"
 import { SessionSummary } from "./summary"
 
-const log = Log.create({ service: "session.revert" })
-
 export const RevertInput = Schema.Struct({
   sessionID: SessionID,
   messageID: MessageID,
@@ -90,7 +87,7 @@ export const layer = Layer.effect(
     })
 
     const unrevert = Effect.fn("SessionRevert.unrevert")(function* (input: { sessionID: SessionID }) {
-      log.info("unreverting", input)
+      yield* Effect.logInfo("unreverting", { sessionID: input.sessionID })
       yield* state.assertNotBusy(input.sessionID)
       const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
       if (!session.revert) return session

+ 3 - 5
packages/opencode/src/session/session.ts

@@ -28,7 +28,6 @@ import { or } from "drizzle-orm"
 import type { SQL } from "drizzle-orm"
 import { PartTable, SessionTable } from "@opencode-ai/core/session/sql"
 import { ProjectTable } from "@opencode-ai/core/project/sql"
-import { Log } from "@opencode-ai/core/util/log"
 import { MessageV2 } from "./message-v2"
 import type { InstanceContext } from "../project/instance-context"
 import { InstanceState } from "@/effect/instance-state"
@@ -46,7 +45,6 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { ModelV2 } from "@opencode-ai/core/model"
 
-const log = Log.create({ service: "session" })
 const runtime = makeRuntime(Database.Service, Database.defaultLayer)
 
 const parentTitlePrefix = "New session - "
@@ -573,7 +571,7 @@ export const layer: Layer.Layer<
           updated: Date.now(),
         },
       }
-      log.info("created", result)
+      yield* Effect.logInfo("created", result)
 
       yield* events.publish(SessionV1.Event.Created, { sessionID: result.id, info: result })
 
@@ -664,8 +662,8 @@ export const layer: Layer.Layer<
 
         yield* events.publish(SessionV1.Event.Deleted, { sessionID, info: session })
         yield* events.remove(sessionID)
-      } catch (e) {
-        log.error(e)
+      } catch (error) {
+        yield* Effect.logError("failed to remove session", { sessionID, error })
       }
     })
 

+ 0 - 4
packages/opencode/src/session/tools.ts

@@ -17,13 +17,10 @@ import { MessageV2 } from "./message-v2"
 import { Session } from "./session"
 import { SessionProcessor } from "./processor"
 import { PartID } from "./schema"
-import { Log } from "@opencode-ai/core/util/log"
 import { EffectBridge } from "@/effect/bridge"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { ModelV2 } from "@opencode-ai/core/model"
 
-const log = Log.create({ service: "session.tools" })
-
 export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
   agent: Agent.Info
   model: Provider.Model
@@ -33,7 +30,6 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
   messages: SessionV1.WithParts[]
   promptOps: TaskPromptOps
 }) {
-  using _ = log.time("resolveTools")
   const tools: Record<string, AITool> = {}
   const run = yield* EffectBridge.make()
   const plugin = yield* Plugin.Service

+ 7 - 13
packages/opencode/src/share/share-next.ts

@@ -13,13 +13,11 @@ import type { SessionID } from "@/session/schema"
 import { Database } from "@opencode-ai/core/database/database"
 import { eq } from "drizzle-orm"
 import { Config } from "@/config/config"
-import * as Log from "@opencode-ai/core/util/log"
 import { SessionShareTable } from "@opencode-ai/core/share/sql"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { ModelV2 } from "@opencode-ai/core/model"
 import { EventV2 } from "@opencode-ai/core/event"
 
-const log = Log.create({ service: "share-next" })
 const disabled = process.env["OPENCODE_DISABLE_SHARE"] === "true" || process.env["OPENCODE_DISABLE_SHARE"] === "1"
 
 export type Api = {
@@ -141,9 +139,7 @@ export const layer = Layer.effect(
         yield* flush(sessionID).pipe(
           Effect.delay(1000),
           Effect.catchCause((cause) =>
-            Effect.sync(() => {
-              log.error("share flush failed", { sessionID, cause })
-            }),
+            Effect.logError("share flush failed", { sessionID: sessionID, cause: cause }),
           ),
           Effect.forkIn(s.scope),
         )
@@ -175,7 +171,7 @@ export const layer = Layer.effect(
             if (event.type !== def.type || event.location?.directory !== _ctx.directory) return Effect.void
             return fn(event.data as EventV2.Data<D>).pipe(
               Effect.catchCause((cause) =>
-                Effect.sync(() => log.error("share subscriber failed", { type: def.type, cause })),
+                Effect.logError("share subscriber failed", { type: def.type, cause: cause }),
               ),
             )
           })
@@ -267,12 +263,12 @@ export const layer = Layer.effect(
       )
 
       if (res.status >= 400) {
-        log.warn("failed to sync share", { sessionID, shareID: share.id, status: res.status })
+        yield* Effect.logWarning("failed to sync share", { sessionID: sessionID, shareID: share.id, status: res.status })
       }
     })
 
     const full = Effect.fn("ShareNext.full")(function* (sessionID: SessionID) {
-      log.info("full sync", { sessionID })
+      yield* Effect.logInfo("full sync", { sessionID: sessionID })
       const info = yield* session.get(sessionID)
       const diffs = yield* session.diff(sessionID)
       const messages = yield* session.messages({ sessionID })
@@ -309,7 +305,7 @@ export const layer = Layer.effect(
 
     const create = Effect.fn("ShareNext.create")(function* (sessionID: SessionID) {
       if (disabled) return { id: "", url: "", secret: "" }
-      log.info("creating share", { sessionID })
+      yield* Effect.logInfo("creating share", { sessionID: sessionID })
       const req = yield* request()
       const result = yield* HttpClientRequest.post(`${req.baseUrl}${req.api.create}`).pipe(
         HttpClientRequest.setHeaders(req.headers),
@@ -330,9 +326,7 @@ export const layer = Layer.effect(
       s.shared.set(sessionID, result)
       yield* full(sessionID).pipe(
         Effect.catchCause((cause) =>
-          Effect.sync(() => {
-            log.error("share full sync failed", { sessionID, cause })
-          }),
+          Effect.logError("share full sync failed", { sessionID: sessionID, cause: cause }),
         ),
         Effect.forkIn(s.scope),
       )
@@ -341,7 +335,7 @@ export const layer = Layer.effect(
 
     const remove = Effect.fn("ShareNext.remove")(function* (sessionID: SessionID) {
       if (disabled) return
-      log.info("removing share", { sessionID })
+      yield* Effect.logInfo("removing share", { sessionID: sessionID })
       const s = yield* InstanceState.get(state)
       const share = yield* getCached(sessionID)
       if (!share) {

+ 11 - 18
packages/opencode/src/skill/discovery.ts

@@ -4,7 +4,6 @@ import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } fr
 import { withTransientReadRetry } from "@/util/effect-http-client"
 import { FSUtil } from "@opencode-ai/core/fs-util"
 import { Global } from "@opencode-ai/core/global"
-import * as Log from "@opencode-ai/core/util/log"
 
 const skillConcurrency = 4
 const fileConcurrency = 8
@@ -27,7 +26,6 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Sk
 export const layer: Layer.Layer<Service, never, FSUtil.Service | Path.Path | HttpClient.HttpClient> = Layer.effect(
   Service,
   Effect.gen(function* () {
-    const log = Log.create({ service: "skill-discovery" })
     const fs = yield* FSUtil.Service
     const path = yield* Path.Path
     const http = HttpClient.filterStatusOk(withTransientReadRetry(yield* HttpClient.HttpClient))
@@ -42,10 +40,7 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Path.Path | Htt
         Effect.flatMap((body) => fs.writeWithDirs(dest, new Uint8Array(body))),
         Effect.as(true),
         Effect.catch((err) =>
-          Effect.sync(() => {
-            log.error("failed to download", { url, err })
-            return false
-          }),
+          Effect.logError("failed to download", { url: url, error: err }).pipe(Effect.as(false)),
         ),
       )
     })
@@ -55,29 +50,27 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Path.Path | Htt
       const index = new URL("index.json", base).href
       const host = base.slice(0, -1)
 
-      log.info("fetching index", { url: index })
+      yield* Effect.logInfo("fetching index", { url: index })
 
       const data = yield* HttpClientRequest.get(index).pipe(
         HttpClientRequest.acceptJson,
         http.execute,
         Effect.flatMap(HttpClientResponse.schemaBodyJson(Index)),
         Effect.catch((err) =>
-          Effect.sync(() => {
-            log.error("failed to fetch index", { url: index, err })
-            return null
-          }),
+          Effect.logError("failed to fetch index", { url: index, error: err }).pipe(Effect.as(null)),
         ),
       )
 
       if (!data) return []
 
-      const list = data.skills.filter((skill) => {
-        if (!skill.files.includes("SKILL.md")) {
-          log.warn("skill entry missing SKILL.md", { url: index, skill: skill.name })
-          return false
-        }
-        return true
-      })
+      const missing = data.skills.filter((skill) => !skill.files.includes("SKILL.md"))
+      yield* Effect.forEach(
+        missing,
+        (skill) =>
+          Effect.logWarning("skill entry missing SKILL.md", { url: index, skill: skill.name }),
+        { discard: true },
+      )
+      const list = data.skills.filter((skill) => skill.files.includes("SKILL.md"))
 
       const dirs = yield* Effect.forEach(
         list,

+ 5 - 12
packages/opencode/src/skill/index.ts

@@ -14,11 +14,9 @@ import { FrontmatterError } from "@opencode-ai/core/v1/config/error"
 import { ConfigMarkdown } from "@/config/markdown"
 import { RuntimeFlags } from "@/effect/runtime-flags"
 import { Glob } from "@opencode-ai/core/util/glob"
-import * as Log from "@opencode-ai/core/util/log"
 import { Discovery } from "./discovery"
 import { isRecord } from "@/util/record"
 
-const log = Log.create({ service: "skill" })
 const CLAUDE_EXTERNAL_DIR = ".claude"
 const AGENTS_EXTERNAL_DIR = ".agents"
 const EXTERNAL_SKILL_PATTERN = "skills/**/SKILL.md"
@@ -113,7 +111,7 @@ const add = Effect.fnUntraced(function* (state: State, match: string, events: Ev
         const message = FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse skill ${match}`
         const { Session } = yield* Effect.promise(() => import("@/session/session"))
         yield* events.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
-        log.error("failed to load skill", { skill: match, err })
+        yield* Effect.logError("failed to load skill", { skill: match, error: err })
         return undefined
       }),
     ),
@@ -124,11 +122,7 @@ const add = Effect.fnUntraced(function* (state: State, match: string, events: Ev
   if (!isSkillFrontmatter(md.data)) return
 
   if (state.skills[md.data.name]) {
-    log.warn("duplicate skill name", {
-      name: md.data.name,
-      existing: state.skills[md.data.name].location,
-      duplicate: match,
-    })
+    yield* Effect.logWarning("duplicate skill name", { name: md.data.name, existing: state.skills[md.data.name].location, duplicate: match })
   }
 
   state.dirs.add(path.dirname(match))
@@ -159,8 +153,7 @@ const scan = Effect.fnUntraced(function* (
   }).pipe(
     Effect.catch((error) => {
       if (!opts?.scope) return Effect.die(error)
-      log.error(`failed to scan ${opts.scope} skills`, { dir: root, error })
-      return Effect.succeed([] as string[])
+      return Effect.logError(`failed to scan ${opts.scope} skills`, { dir: root, error: error }).pipe(Effect.as([] as string[]))
     }),
   )
 
@@ -212,7 +205,7 @@ const discoverSkills = Effect.fnUntraced(function* (
     const expanded = item.startsWith("~/") ? path.join(global.home, item.slice(2)) : item
     const dir = path.isAbsolute(expanded) ? expanded : path.join(directory, expanded)
     if (!(yield* fsys.isDir(dir))) {
-      log.warn("skill path not found", { path: dir })
+      yield* Effect.logWarning("skill path not found", { path: dir })
       continue
     }
 
@@ -242,7 +235,7 @@ const loadSkills = Effect.fnUntraced(function* (
     discard: true,
   })
 
-  log.info("init", { count: Object.keys(state.skills).length })
+  yield* Effect.logInfo("init", { count: Object.keys(state.skills).length })
 })
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/Skill") {}

Некоторые файлы не были показаны из-за большого количества измененных файлов