Get this on every PR automatically. Webhook fires, AI reviews, you approve.
Install on langchain-ai/openwiki →
// reviewed by pulllight

PullLight found 5 issues

langchain-ai/openwiki #280 — feat: add Grok Build subscription CLI as a provider by @sanjay3290 Jul 16, 2026
2 high 2 medium 1 low
🔍
// convert to your repo
PullLight caught 5 2 high, 2 medium, 1 low on this PR.
Install on langchain-ai/openwiki to get this on every PR automatically — 60 seconds.
Findings
5 issues
high resource-leak
The `timeout` timer is only cleared on the happy path (`clearTimeout(timeout)` after `await stdoutDone`). If `writeFile` throws, or if `spawn` throws, or if any `await` between the spawn and `clearTimeout(timeout)` rejects, the `finally` block runs `unlink(promptFilePath)` but `timeout` is never cleared. The dangling timer will fire after the function has already thrown, calling `killProcessGroup` on a potentially recycled PID. Move `clearTimeout(timeout)` into the `finally` block (or use `clearTimeout` unconditionally before re-throwing).
// src/agent/engines/runner.ts:163
high race-condition
`liveProcessGroupIds` and `threadSessionIds` are module-level mutable `Map`/`Set` shared across all concurrent `runAgentCli` calls. If two runs execute concurrently for the same `threadId`, `setThreadSessionId` from the first run can be overwritten by the second before the first's `outcome.sessionId` is stored, silently losing session continuity. More critically, `liveProcessGroupIds` is iterated in signal handlers while being mutated from async callbacks — no synchronisation exists. This is a structural race; at minimum document the single-run-per-thread assumption and enforce it, or use per-invocation state.
// src/agent/engines/runner.ts:155
medium logic
The `binary` value is resolved from `process.env[providerConfig.binaryEnvKey]?.trim() || providerConfig.defaultBinary`. Using `||` means an env var set to an empty string (after trim) silently falls back to the default binary rather than failing with a clear error. This is a minor logic issue but could mask misconfiguration.
// src/agent/engines/runner.ts:130
medium logic
`listFilesModifiedSince` uses `stat` (follows symlinks) to check mtime, but `entry.isSymbolicLink()` is also allowed through. A symlink whose target was modified before `sinceMs` but whose link itself was created after will be included; conversely a symlink created before `sinceMs` pointing to a freshly-written file will be missed. More importantly, a symlink pointing outside `rootDir` (e.g. to `/etc/passwd`) will be `stat`-followed and its mtime checked — the relative path will still be inside `rootDir` so `isAllowedDocsOnlyWritePath` will evaluate the link name, not the target. An agent that creates a symlink `openwiki/evil -> /etc/passwd` would pass the boundary check. Consider using `lstat` instead of `stat` for symlinks, or explicitly rejecting symlinks.
// src/agent/engines/write-boundary.ts:97
low auth
`shouldPassEnvKey` blocks `OPENWIKI_*` and `LANGCHAIN_*` prefixes but the allowlist `PASSTHROUGH_ENV_KEYS` is checked last with `PASSTHROUGH_ENV_KEYS.has(key) || key === "LANG"`. Because the prefix checks return `false` early, an `OPENWIKI_*` key can never reach the allowlist — that part is correct. However, `HTTP_PROXY` / `HTTPS_PROXY` are passed through unconditionally. A compromised or prompt-injected child process that reads these could exfiltrate data through the proxy. This is an accepted trade-off for corporate CA setups, but it should be explicitly documented as a known risk.
// src/agent/engines/child-env.ts:100
model: claude-sonnet-4-5   input: 31201 tokens   output: 1273 tokens