fix(server): emit fixed workspace fence headers (#26443)
Some checks failed
deploy / deploy (push) Waiting to run
generate / generate (push) Waiting to run
nix-eval / nix-eval (push) Waiting to run
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404, x86_64-linux) (push) Waiting to run
nix-hashes / compute-hash (blacksmith-4vcpu-ubuntu-2404-arm, aarch64-linux) (push) Waiting to run
nix-hashes / compute-hash (macos-15-intel, x86_64-darwin) (push) Waiting to run
nix-hashes / compute-hash (macos-latest, aarch64-darwin) (push) Waiting to run
nix-hashes / update-hashes (push) Blocked by required conditions
publish / build-cli (push) Blocked by required conditions
publish / version (push) Waiting to run
publish / sign-cli-windows (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=arm64 host:macos-26 platform_flag:--mac --arm64 target:aarch64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[bun_install_flags:--os=darwin --cpu=x64 host:macos-26-intel platform_flag:--mac --x64 target:x86_64-apple-darwin]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:aarch64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-ubuntu-2404 platform_flag:--linux target:x86_64-unknown-linux-gnu]) (push) Blocked by required conditions
publish / build-electron (map[host:blacksmith-4vcpu-windows-2025 platform_flag:--win target:x86_64-pc-windows-msvc]) (push) Blocked by required conditions
publish / build-electron (map[host:windows-2025 platform_flag:--win --arm64 target:aarch64-pc-windows-msvc]) (push) Blocked by required conditions
publish / publish (push) Blocked by required conditions
storybook / storybook build (push) Waiting to run
test / unit (linux) (push) Waiting to run
test / unit (windows) (push) Waiting to run
test / e2e (linux) (push) Waiting to run
test / e2e (windows) (push) Waiting to run
typecheck / typecheck (push) Waiting to run
docs-locale-sync / sync-locales (push) Has been cancelled
containers / build (push) Has been cancelled

This commit is contained in:
Kit Langton 2026-05-08 22:45:54 -04:00 committed by GitHub
parent 9b7b6cb30f
commit 8e9550d90d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 0 deletions

View file

@ -0,0 +1,20 @@
import { Flag } from "@opencode-ai/core/flag/flag"
import { Effect } from "effect"
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import * as Fence from "@/server/shared/fence"
const ignoredMethods = new Set(["GET", "HEAD", "OPTIONS"])
export const fenceLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest
if (!Flag.OPENCODE_WORKSPACE_ID || ignoredMethods.has(request.method)) return yield* effect
const previous = Fence.load()
const response = yield* effect
const current = Fence.diff(previous, Fence.load())
if (Object.keys(current).length === 0) return response
return HttpServerResponse.setHeader(response, Fence.HEADER, JSON.stringify(current))
}),
).layer

View file

@ -82,6 +82,7 @@ import { disposeMiddleware } from "./lifecycle"
import { memoMap } from "@opencode-ai/core/effect/memo-map"
import * as ServerBackend from "@/server/backend"
import { errorLayer } from "./middleware/error"
import { fenceLayer } from "./middleware/fence"
export const context = Context.makeUnsafe<unknown>(new Map())
@ -173,6 +174,7 @@ export function createRoutes(corsOptions?: CorsOptions) {
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
Layer.provide([
errorLayer,
fenceLayer,
cors(corsOptions),
runtime,
Account.defaultLayer,

View file

@ -4,8 +4,11 @@ import { describe, expect } from "bun:test"
import { Config, Effect, FileSystem, Layer, Path } from "effect"
import { HttpClient, HttpClientRequest, HttpRouter, HttpServer } from "effect/unstable/http"
import * as Socket from "effect/unstable/socket/Socket"
import { WorkspaceID } from "../../src/control-plane/schema"
import { InstancePaths } from "../../src/server/routes/instance/httpapi/groups/instance"
import { SessionPaths } from "../../src/server/routes/instance/httpapi/groups/session"
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
import { HEADER as FenceHeader } from "../../src/server/shared/fence"
import { resetDatabase } from "../fixture/db"
import { disposeAllInstances, tmpdirScoped } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
@ -65,6 +68,28 @@ describe("instance HttpApi", () => {
}),
)
it.live("emits a sync fence header for fixed-workspace mutations", () =>
Effect.gen(function* () {
const originalWorkspaceID = Flag.OPENCODE_WORKSPACE_ID
Flag.OPENCODE_WORKSPACE_ID = WorkspaceID.ascending()
yield* Effect.addFinalizer(() =>
Effect.sync(() => {
Flag.OPENCODE_WORKSPACE_ID = originalWorkspaceID
}),
)
const dir = yield* tmpdirScoped({ git: true })
const response = yield* HttpClientRequest.post(SessionPaths.create).pipe(
directoryHeader(dir),
HttpClientRequest.bodyJson({ title: "fenced" }),
Effect.flatMap(HttpClient.execute),
)
expect(response.status).toBe(200)
expect(JSON.parse(response.headers[FenceHeader] ?? "{}")).not.toEqual({})
}),
)
it.live("serves path and VCS read endpoints", () =>
Effect.gen(function* () {
const dir = yield* tmpdirScoped({ git: true })