summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-15 22:53:10 -0400
committerGitHub <[email protected]>2026-04-16 02:53:10 +0000
commit702f7412676deae8317213a56a3b32095dba5aa4 (patch)
treeb0832ab0461dd96e784b0d960b63d99a30eee1fc
parent665a8430864fb7a55dedea63a6cbdbe400218f80 (diff)
downloadopencode-702f7412676deae8317213a56a3b32095dba5aa4.tar.gz
opencode-702f7412676deae8317213a56a3b32095dba5aa4.zip
feat: enable oxlint suspicious category, fix 24 violations (#22727)
-rw-r--r--.oxlintrc.json22
-rw-r--r--github/index.ts4
-rw-r--r--packages/app/src/context/global-sdk.tsx1
-rw-r--r--packages/app/src/utils/runtime-adapters.test.ts2
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx2
-rw-r--r--packages/desktop-electron/src/main/apps.ts2
-rw-r--r--packages/function/src/api.ts1
-rw-r--r--packages/opencode/script/postinstall.mjs2
-rw-r--r--packages/opencode/src/bus/bus-event.ts2
-rw-r--r--packages/opencode/src/cli/cmd/debug/agent.ts1
-rw-r--r--packages/opencode/src/cli/cmd/github.ts3
-rw-r--r--packages/opencode/src/cli/cmd/web.ts2
-rw-r--r--packages/opencode/src/patch/patch.ts2
-rw-r--r--packages/opencode/src/server/instance/tui.ts2
-rw-r--r--packages/opencode/src/session/prompt.ts3
-rw-r--r--packages/opencode/src/sync/sync-event.ts2
-rw-r--r--packages/opencode/src/tool/read.ts2
-rw-r--r--packages/opencode/src/tool/tool.ts2
-rw-r--r--packages/opencode/test/mcp/lifecycle.test.ts3
-rw-r--r--packages/opencode/test/session/prompt.test.ts11
20 files changed, 49 insertions, 22 deletions
diff --git a/.oxlintrc.json b/.oxlintrc.json
index c366084ee..37d91f425 100644
--- a/.oxlintrc.json
+++ b/.oxlintrc.json
@@ -1,5 +1,8 @@
{
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
+ "categories": {
+ "suspicious": "warn"
+ },
"rules": {
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
"require-yield": "off",
@@ -10,7 +13,24 @@
// Intentional control char matching (ANSI escapes, null byte sanitization)
"no-control-regex": "off",
// SST and plugin tools require triple-slash references
- "triple-slash-reference": "off"
+ "triple-slash-reference": "off",
+
+ // Suspicious category: suppress noisy rules
+ // Effect's nested function* closures inherently shadow outer scope
+ "no-shadow": "off",
+ // Namespace-heavy codebase makes this too noisy
+ "unicorn/consistent-function-scoping": "off",
+ // Opinionated — .sort()/.reverse() mutation is fine in this codebase
+ "unicorn/no-array-sort": "off",
+ "unicorn/no-array-reverse": "off",
+ // Not relevant — this isn't a DOM event handler codebase
+ "unicorn/prefer-add-event-listener": "off",
+ // Bundler handles module resolution
+ "unicorn/require-module-specifiers": "off",
+ // postMessage target origin not relevant for this codebase
+ "unicorn/require-post-message-target-origin": "off",
+ // Side-effectful constructors are intentional in some places
+ "no-new": "off"
},
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts"]
}
diff --git a/github/index.ts b/github/index.ts
index be8e5aafc..4463aa200 100644
--- a/github/index.ts
+++ b/github/index.ts
@@ -542,7 +542,7 @@ async function subscribeSessionEvents() {
? JSON.stringify(part.state.input)
: "Unknown"
console.log()
- console.log(color + `|`, "\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`, "", "\x1b[0m" + title)
+ console.log(`${color}|`, `\x1b[0m\x1b[2m ${tool.padEnd(7, " ")}`, "", `\x1b[0m${title}`)
}
if (part.type === "text") {
@@ -776,7 +776,7 @@ async function assertPermissions() {
console.log(` permission: ${permission}`)
} catch (error) {
console.error(`Failed to check permissions: ${error}`)
- throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
+ throw new Error(`Failed to check permissions for user ${actor}: ${error}`, { cause: error })
}
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx
index 172b5c966..e53d60d5a 100644
--- a/packages/app/src/context/global-sdk.tsx
+++ b/packages/app/src/context/global-sdk.tsx
@@ -128,6 +128,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
if (started) return run
started = true
run = (async () => {
+ // oxlint-disable-next-line no-unmodified-loop-condition -- `started` is set to false by stop() which also aborts; both flags are checked to allow graceful exit
while (!abort.signal.aborted && started) {
attempt = new AbortController()
lastEventAt = Date.now()
diff --git a/packages/app/src/utils/runtime-adapters.test.ts b/packages/app/src/utils/runtime-adapters.test.ts
index 9f408b8eb..49552e179 100644
--- a/packages/app/src/utils/runtime-adapters.test.ts
+++ b/packages/app/src/utils/runtime-adapters.test.ts
@@ -46,7 +46,9 @@ describe("runtime adapters", () => {
})
test("resolves speech recognition constructor with webkit precedence", () => {
+ // oxlint-disable-next-line no-extraneous-class
class SpeechCtor {}
+ // oxlint-disable-next-line no-extraneous-class
class WebkitCtor {}
const ctor = getSpeechRecognitionCtor({
SpeechRecognition: SpeechCtor,
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
index 90c9d7a2e..a25963ab0 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
@@ -90,7 +90,7 @@ export function ReloadSection() {
}
const info = billingInfo()!
setStore("show", true)
- setStore("reload", info.reload ? true : true)
+ setStore("reload", true)
setStore("reloadAmount", info.reloadAmount.toString())
setStore("reloadTrigger", info.reloadTrigger.toString())
}
diff --git a/packages/desktop-electron/src/main/apps.ts b/packages/desktop-electron/src/main/apps.ts
index d21b6cc9e..174da94a5 100644
--- a/packages/desktop-electron/src/main/apps.ts
+++ b/packages/desktop-electron/src/main/apps.ts
@@ -28,7 +28,7 @@ export function wslPath(path: string, mode: "windows" | "linux" | null): string
const output = execFileSync("wsl", ["-e", "wslpath", flag, path])
return output.toString().trim()
} catch (error) {
- throw new Error(`Failed to run wslpath: ${String(error)}`)
+ throw new Error(`Failed to run wslpath: ${String(error)}`, { cause: error })
}
}
diff --git a/packages/function/src/api.ts b/packages/function/src/api.ts
index 68b2d450b..58c74fe32 100644
--- a/packages/function/src/api.ts
+++ b/packages/function/src/api.ts
@@ -13,6 +13,7 @@ type Env = {
}
export class SyncServer extends DurableObject<Env> {
+ // oxlint-disable-next-line no-useless-constructor
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env)
}
diff --git a/packages/opencode/script/postinstall.mjs b/packages/opencode/script/postinstall.mjs
index 2b990251c..7dcf3958a 100644
--- a/packages/opencode/script/postinstall.mjs
+++ b/packages/opencode/script/postinstall.mjs
@@ -64,7 +64,7 @@ function findBinary() {
return { binaryPath, binaryName }
} catch (error) {
- throw new Error(`Could not find package ${packageName}: ${error.message}`)
+ throw new Error(`Could not find package ${packageName}: ${error.message}`, { cause: error })
}
}
diff --git a/packages/opencode/src/bus/bus-event.ts b/packages/opencode/src/bus/bus-event.ts
index aad5f398e..369a40ed8 100644
--- a/packages/opencode/src/bus/bus-event.ts
+++ b/packages/opencode/src/bus/bus-event.ts
@@ -25,7 +25,7 @@ export namespace BusEvent {
properties: def.properties,
})
.meta({
- ref: "Event" + "." + def.type,
+ ref: `Event.${def.type}`,
})
})
.toArray()
diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts
index 6c7ad39c1..29d6ace59 100644
--- a/packages/opencode/src/cli/cmd/debug/agent.ts
+++ b/packages/opencode/src/cli/cmd/debug/agent.ts
@@ -111,6 +111,7 @@ function parseToolParams(input?: string) {
} catch (evalError) {
throw new Error(
`Failed to parse --params. Use JSON or a JS object literal. JSON error: ${jsonError}. Eval error: ${evalError}.`,
+ { cause: evalError },
)
}
}
diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts
index 191aa2dfd..46d091642 100644
--- a/packages/opencode/src/cli/cmd/github.ts
+++ b/packages/opencode/src/cli/cmd/github.ts
@@ -1031,6 +1031,7 @@ export const GithubRunCommand = cmd({
console.error("Failed to get OIDC token:", error instanceof Error ? error.message : error)
throw new Error(
"Could not fetch an OIDC token. Make sure to add `id-token: write` to your workflow permissions.",
+ { cause: error },
)
}
}
@@ -1221,7 +1222,7 @@ export const GithubRunCommand = cmd({
console.log(` permission: ${permission}`)
} catch (error) {
console.error(`Failed to check permissions: ${error}`)
- throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
+ throw new Error(`Failed to check permissions for user ${actor}: ${error}`, { cause: error })
}
if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts
index e656c83d9..9dd8796d6 100644
--- a/packages/opencode/src/cli/cmd/web.ts
+++ b/packages/opencode/src/cli/cmd/web.ts
@@ -34,7 +34,7 @@ export const WebCommand = cmd({
describe: "start opencode server and open web interface",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
- UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
+ UI.println(UI.Style.TEXT_WARNING_BOLD + "! OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = await Server.listen(opts)
diff --git a/packages/opencode/src/patch/patch.ts b/packages/opencode/src/patch/patch.ts
index d36ec72c7..749efd911 100644
--- a/packages/opencode/src/patch/patch.ts
+++ b/packages/opencode/src/patch/patch.ts
@@ -313,7 +313,7 @@ export function deriveNewContentsFromChunks(filePath: string, chunks: UpdateFile
try {
originalContent = readFileSync(filePath, "utf-8")
} catch (error) {
- throw new Error(`Failed to read file ${filePath}: ${error}`)
+ throw new Error(`Failed to read file ${filePath}: ${error}`, { cause: error })
}
let originalLines = originalContent.split("\n")
diff --git a/packages/opencode/src/server/instance/tui.ts b/packages/opencode/src/server/instance/tui.ts
index 13f150655..0073ef98c 100644
--- a/packages/opencode/src/server/instance/tui.ts
+++ b/packages/opencode/src/server/instance/tui.ts
@@ -339,7 +339,7 @@ export const TuiRoutes = lazy(() =>
properties: def.properties,
})
.meta({
- ref: "Event" + "." + def.type,
+ ref: `Event.${def.type}`,
})
}),
),
diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts
index 7a7493903..f04ea8cde 100644
--- a/packages/opencode/src/session/prompt.ts
+++ b/packages/opencode/src/session/prompt.ts
@@ -260,8 +260,7 @@ export namespace SessionPrompt {
messageID: userMessage.info.id,
sessionID: userMessage.info.sessionID,
type: "text",
- text:
- BUILD_SWITCH + "\n\n" + `A plan file exists at ${plan}. You should execute on the plan defined within it`,
+ text: `${BUILD_SWITCH}\n\nA plan file exists at ${plan}. You should execute on the plan defined within it`,
synthetic: true,
})
userMessage.parts.push(part)
diff --git a/packages/opencode/src/sync/sync-event.ts b/packages/opencode/src/sync/sync-event.ts
index 2b1eb0981..bee7e3c4c 100644
--- a/packages/opencode/src/sync/sync-event.ts
+++ b/packages/opencode/src/sync/sync-event.ts
@@ -273,7 +273,7 @@ export function payloads() {
data: def.schema,
})
.meta({
- ref: "SyncEvent" + "." + def.type,
+ ref: `SyncEvent.${def.type}`,
})
})
.toArray()
diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts
index 701bfc4b9..4dc984d0e 100644
--- a/packages/opencode/src/tool/read.ts
+++ b/packages/opencode/src/tool/read.ts
@@ -181,7 +181,7 @@ export const ReadTool = Tool.define(
)
}
- let output = [`<path>${filepath}</path>`, `<type>file</type>`, "<content>" + "\n"].join("\n")
+ let output = [`<path>${filepath}</path>`, `<type>file</type>`, "<content>\n"].join("\n")
output += file.raw.map((line, i) => `${i + file.offset}: ${line}`).join("\n")
const last = file.offset + file.raw.length - 1
diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts
index 30be63a32..ca2586234 100644
--- a/packages/opencode/src/tool/tool.ts
+++ b/packages/opencode/src/tool/tool.ts
@@ -78,7 +78,7 @@ export namespace Tool {
) {
return () =>
Effect.gen(function* () {
- const toolInfo = init instanceof Function ? { ...(yield* init()) } : { ...init }
+ const toolInfo = typeof init === "function" ? { ...(yield* init()) } : { ...init }
const execute = toolInfo.execute
toolInfo.execute = (args, ctx) => {
const attrs = {
diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts
index 09caa1cd8..add7c66d9 100644
--- a/packages/opencode/test/mcp/lifecycle.test.ts
+++ b/packages/opencode/test/mcp/lifecycle.test.ts
@@ -53,6 +53,7 @@ function getOrCreateClientState(name?: string): MockClientState {
class MockStdioTransport {
stderr: null = null
pid = 12345
+ // oxlint-disable-next-line no-useless-constructor
constructor(_opts: any) {}
async start() {
if (connectShouldHang) return new Promise<void>(() => {}) // never resolves
@@ -64,6 +65,7 @@ class MockStdioTransport {
}
class MockStreamableHTTP {
+ // oxlint-disable-next-line no-useless-constructor
constructor(_url: URL, _opts?: any) {}
async start() {
if (connectShouldHang) return new Promise<void>(() => {}) // never resolves
@@ -76,6 +78,7 @@ class MockStreamableHTTP {
}
class MockSSE {
+ // oxlint-disable-next-line no-useless-constructor
constructor(_url: URL, _opts?: any) {}
async start() {
if (connectShouldHang) return new Promise<void>(() => {}) // never resolves
diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts
index 1290570b8..4f5b19bca 100644
--- a/packages/opencode/test/session/prompt.test.ts
+++ b/packages/opencode/test/session/prompt.test.ts
@@ -60,12 +60,11 @@ function chat(text: string) {
function hanging(ready: () => void) {
const encoder = new TextEncoder()
let timer: ReturnType<typeof setTimeout> | undefined
- const first =
- `data: ${JSON.stringify({
- id: "chatcmpl-1",
- object: "chat.completion.chunk",
- choices: [{ delta: { role: "assistant" } }],
- })}` + "\n\n"
+ const first = `data: ${JSON.stringify({
+ id: "chatcmpl-1",
+ object: "chat.completion.chunk",
+ choices: [{ delta: { role: "assistant" } }],
+ })}\n\n`
const rest =
[
`data: ${JSON.stringify({