I build AI features end to end: the model, the service that serves it, and the interface on top — prototype to production, without a handoff. Much of my work is local-first by default, so retrieval and inference run on your own infrastructure and customer data never leaves it. My fixes are merged into n8n and openclaw, mostly the unglamorous kind — a silent data-loss bug, a cross-platform breakage, a missing CI pipeline — each one pinned with a regression test. I'd rather ship what survives production than what demos well.
- Developing:Maestro — an open-core orchestrator for multi-agent LLM workflows, self-hosted and bring-your-own-key
- Focus: Production RAG, MLOps, and applied ML
- Open to: Collaboration on scalable RAG / LLM systems
| AI / ML | |
| RAG / Data | |
| Backend | |
| Frontend | |
| DevOps | |
| Security |
openclaw/openclaw
· 20 merged
- P0 release blocker: OpenClaw installed but the managed Gateway never started for Windows users whose profile path holds non-ASCII characters outside the CJK range — the generated
.cmdlauncher was written in UTF-8, butcmd.exeparses batch files with the boot-time OEM code page. Fixed across 15 OEM code pages; verified on a Turkish host (ev-yiğit-öğün, ACP 1254 / OEMCP 857) going fromMODULE_NOT_FOUNDto a clean start, with byte-identical output on CJK hosts (PR #108967) - A compacted session's summary grew a copy of itself every cycle, burning tokens on each following turn: whenever a later split degraded, staged compaction re-added a summary chunk 0 already carried (occurrences 2 → 1 after the fix). Fixed in 14 source lines by keying the fallback on whether the oldest split degraded — the one signal the consumer actually asks for — with regression coverage at both layers (PR #109828)
- Every file an agent created on Windows came back lowercased — any name not already lowercase — so the import it had just written broke for all teammates on Linux and in CI; Turkish names like
İstanbul.mdcame back corrupted, not merely lowercased. The sandbox helper was returning a comparison key as a real path — fixed with a case-preserving normalizer, every boundary verdict proven unchanged (PR #109823) - Every truncated turn was billed by the provider but recorded as free — zero tokens, zero cost, and a
stopreason marking it successful, so nothing downstream could retry or warn and per-session accounting drifted low. Any answer hitting the output cap endsincomplete, and that event matched no terminal branch in the agent transport. Fixed by finalizing both terminal events through one canonical usage mapper shared with the package side (PR #109904) - A reply whose code fence opened with a long info string was dropped outright: the chunker reopened the block on every continuation with that full opening line while budgeting only the closing marker, so chunks ran past Discord's 2000-character limit and were rejected with HTTP 400. On a worst-case fence, 1534 chunks with 41 over the limit became 4 that all send, with the body intact instead of 41 of 1290 characters surviving (PR #110148)
- Invalid credentials took 7093ms and 4 requests to report; now 3ms and 1. The retry loop threw its non-retryable errors inside the
trywhosecatchtreats everything as a retryable network failure, swallowing its own classification. Rethrown as a distinct type, same message to the user (PR #110655) - Reef died permanently to relay rate limiting and had to be restarted by hand — a throttled startup failed the whole account, the supervisor's ten restarts each hit the same relay, and the retry traffic fed the throttling that caused it. Startup now shares the periodic reconcile's failure policy: the account comes up on the peer keys it already has and refreshes on the next interval (PR #110918)
- A rate-limited turn failed instead of simply waiting out the server's cooldown: when a 429 carried an unparseable
retry-after-ms, the validRetry-Aftersitting next to it was never read, so the client fell back to blind exponential backoff and burned its attempts inside the window. The two headers are ordered preferences, not alternatives — returning only on a successful parse restored that. Measured against a real socket, the retry gap went from 1016ms of blind backoff to the 3018ms the server asked for (PR #111353) - Coloured command output came back as literal escape codes —
npm install, a coloured test run, adocker build— whenever a sequence happened to straddle a stream read, and the corrupted text landed in the transcript the model reads. The remote bash path already kept a per-stream ANSI parser; the localexecruntime still called the stateless helper on every chunk. Wired the same parser in, with separate state for stdout and stderr so one stream can't consume the other's pending sequence — proven against a real child process splitting a sequence across a real pipe (PR #111364) - Every OpenRouter agent turn dropped its cache-write tokens and billed them as ordinary input — the agent lane hard-coded
cacheWrite: 0while its sibling parser three files over already read the field and documented the contract. Correcting the mapping alone would have silently shrunk every overflow decision on that lane, because the context-overflow fallback was accidentally exact only whileinputabsorbed the writes — so both land together, proven over a real socket with a real transport:80/0→70/10tokens, and a real overflow that the half-fix reports asfalse(PR #111435) - A browser that never came back permanently bricked tab tracking for its profile — from then on every
browser openopened a tab, closed it again, and errored, with manual state clearing the only recovery. Cleanup defers whenever it cannot prove ownership of a tab and nothing ever dropped a row by age, so rows for a dead browser were re-claimed, failed and deferred every 5 minutes forever, until they filled the tracking store's 5,000-rowreject-newcap. Bounded the retry by retiring only rows whose ownership probe already failed and that have gone unused past a 24h window — unlike a namespace TTL, which would also expire tabs that are alive and reachable. Proven end to end against real Chrome over real CDP and real SQLite, across two processes so the row is read back after a restart, withnowthe only injected input (PR #111307) - Host execution blocked
CC,CPPandCXXas compiler selectors but still acceptedCXXCPP— GNU Autoconf's C++ preprocessor selector, the exact counterpart of theCPPthat was already blocked everywhere, so an operator-chosen preprocessor executable could still reach a host build through the inherited or the requested environment. Closing the C++ half of the same rule makes the boundary explainable instead of accidental: one canonical policy key, mirrored into the generated Swift policy and the reported baseline (265 → 266 entries). Proven with the production sanitizer against a real child process — inherited and requestedCXXCPPboth becomenull, the requested one is reported inrejectedOverrideBlockedKeys, benign controls survive and the child exits 0 with noCXXCPPin its environment (PR #112684) - The
edittool refused a perfectly unambiguous edit as ambiguous — "Found 2 occurrences … the text must be unique" for text that occurs exactly once — whenever another line in the file differed only by trailing whitespace, a smart quote, an en-dash or a non-breaking space, which files mixing straight and curly quotes hit routinely. The match and the safety check ran in two different string spaces: the exact path found the text in raw content, then the uniqueness gate counted after normalization had folded away the very distinctions the match relied on. The cost fell on the model, which is told to add context and so retries with a largeroldTextthat spans another near-duplicate and is refused again. Counting in whichever space the match was actually found in fixes it, with a control case proving a genuinely fuzzy-ambiguous edit is still refused (PR #115738) - The compat layer that makes xAI, Venice, Fireworks and LM Studio models usable reported success while leaving the banned keyword in the request — the provider refused the tool call anyway, so from the user's side the model simply could not call the tool and the setting looked like it did nothing. The strip walked five schema containers and copied everything else through verbatim, so a keyword nested under
additionalProperties,prefixItems,patternProperties,contains,$defsand six more survived — andadditionalPropertiesholding a schema is the ordinary way to describe a dictionary, common in MCP tool definitions. Fixed by mirroring the container sets its own caller already agreed on. Proven over a real socket through the real transport, nofetchstub:additionalPropertiesarrives as{"maxLength":100,"type":"string"}onmainand{"type":"string"}here, with three regression tests that each fail against the unfixed source (PR #115741) - One unparseable streaming frame pasted the model's own output straight into the error surface — tool results, file contents it had just read, any credential it had generated — because the Anthropic provider built its parse-failure message out of the raw frame, embedding both the
datapayload and every raw line. The answer already existed one directory over: the canonical Anthropic transport converts only aSyntaxErrorinto a shared malformed-fragment marker and keeps the original error ascause, carrying no payload. What makes this a broken user-facing contract rather than a merely verbose error is who reads that marker — the shared assistant-error formatter matches it by exact string equality and swaps in "LLM streaming response contained a malformed fragment. Please try again.", so the provider path, emitting a different string, never reached that substitution and the operator saw the fragment itself. Aligning the provider with the marker took the operator-facing text from 402 bytes of echoed payload to a 54-byte retry message. The neighbouring throw is deliberately left alone: its structurederrorbody is parsed downstream into a meaningful operator message, so redacting that one would be the regression. Proven over a real loopback SSE server through the Anthropic SDK and the production provider stream, with a before/after mutation run showing the sentinel leaking on the base behaviour but not on this branch, and a well-formed stream as the control case (PR #116938) - The same broken contract, one provider over: a malformed frame on a ChatGPT-login model surfaced the parser's own internal wording instead of the actionable retry message every other provider gives, because the Codex SSE boundary rethrew
JSON.parse's failure as a string of its own. Three separate consumers match the shared malformed-fragment marker by exact string equality — the assistant error formatter, the sanitize path, and the fallback sitting directly under the embedded agent's "never return raw unhandled errors" comment — so none recognised it and that last one returned the raw text verbatim. Fixed the way the canonical Anthropic transport already does it: catch onlyJSON.parse'sSyntaxError, convert it to the shared marker, keep the original ascause, and yield outside the catch so aSyntaxErrorinjected by a consumer throughiterator.throw()still propagates untouched. The WebSocket twin in the same file is deliberately excluded, with the reason written into the PR — a line-level scan of open PRs found one asserting on that exact message text and another restructuring the block. Operator-facing text went from 79 bytes of parser wording to the 54-byte retry message, proven over a real loopback SSE server through the production stream, with the mutation run as the before column and a well-formed stream as the control (PR #116966) - An oversized Google auth response left its socket open behind the expected size error, and nothing was logged — the size guard in the Google Chat auth transport reads
content-lengthand throws before it ever reachesresponse.body.getReader(), so thefinallyreleased the SSRF-guarded dispatcher with a live, unread body sitting behind the failure. I found this second guard site while reviewing the fix for the first one (PR #111290, someone else's): that one merged covering only the API wrapper, and this is the follow-up it invited — same predicate, same deliberately non-awaited cancellation, so both guard sites in the extension now read alike, and only the guard's own rejection path is touched. Proven with a loopback server, the real guarded fetch and the realrelease, with the wrapper only recording whatrelease()observes before delegating:bodyUsedgoes fromfalsetotrueon the oversized path, while a normally read response is unchanged as the control (PR #115873) - The assistant’s own prior turn was replayed to the model with its sentences fused together on every OpenAI-compatible provider — OpenRouter, Groq, DeepSeek, Together, LM Studio, Ollama — because
convertMessagesflattened a multi-block assistant turn withjoin(""). Two text blocks came back as"Let me check the file.The file contains X.", and since that corrupted text is what the model reads as its own previous turn, the damage compounds with every subsequent request. Two text blocks in one turn is routine rather than a corner case: streaming opens a new block after any tool call, and cross-model replay converts athinkingblock into atextblock adjacent to the real answer. Every neighbouring path already disagreed with the choice — the thinking blocks a few lines below join with `
, and flattenCompletionMessagesToStringContent, the helper performing this exact operation for strict OpenAI-compatible servers, joins with — so the fix follows the closest sibling instead of inventing a separator. Proven on the wire rather than in a mock: a realnode:httpserver stands in for the provider, parses the actual request body and answers with a real SSE stream through the realstreamOpenAICompletionspath, showingfile.The fileonmainagainstfile.
The file` here (PR #115743)
- Generating speech produced an unplayable file instead of the provider's error on xAI, Gradium, Azure Speech and OpenAI: whenever one of them answered HTTP 200 with a body that was not audio — a JSON error, an
application/problem+jsonpayload, an HTML sign-in or captcha page, or zero bytes — that body was read straight into the buffer and delivered as a voice message, while the provider's actual message was discarded. The repository already owns this contract and one speech path already used it, so whether a malformed 200 was caught depended only on which provider a user happened to route through. The split form (assertProviderBinaryResponseContent+readResponseWithLimit) is applied rather than the combined helper, because the combined one hardcodes its own overflow handler after spreading caller options and would have rewritten each provider's existing byte-cap message — the same reason two video-side extensions use the split form. Cancellation stays deliberately non-awaited: under debug-proxy capture the body is one branch of aResponse.clone()tee, and cancelling such a branch never settles while its sibling is live. Proven against a realnode:httpserver that answers 200 and then never ends the body, driven through the real globalfetchand the real SSRF guard with no stubs: the unguarded path hangs to its deadline with the socket still open (5029 ms) against a named error and an operating-system-observed socket close here (120 ms). The mutation control shows the defect in its rawest form — with the guard removed the malformed cases resolve toBuffer[ 123, 34, 101, 114, 114, … ]andBuffer[ 60, 104, 116, 109, 108, … ], which are{"errand<htmlhanded back as the audio a channel then sends. The maintainer's follow-up added the fourth owner,extensions/openai/tts.ts, and audited all eight bundled TTS owners to confirm the gap was exactly these four (PR #117345) - Kilocode models were registered with a context window larger than the model can actually accept — by up to 3.8x, on 33 of the 335 models the live catalog serves. Kilocode's gateway returns an OpenRouter-shaped catalog in which
context_lengthis the catalog-wide ceiling across every routing candidate, whiletop_provider.context_lengthdescribes the primary provider that actually serves the request; discovery read only the first, so context budgeting and the model metadata users see both overstated what a request could use. The repository already owns the correct precedence —extensions/openrouter/provider-catalog.tsprefers the primary provider and falls back to the catalog-wide value — and the same normalization had just landed insrc/agents/model-scan.ts(PR #110855, someone else's, where I had measured and resolved the merge conflict); the Kilocode reader was the surface that pass missed, so this is a completed rule rather than a new one. What made the claim measurable rather than plausible is that the catalog is public:api.kilo.ai/api/gateway/modelsanswers with no key and no inference call, so the real 346-row response was fed through the actualdiscoverKilocodeModels()implementation with the production file as the only variable between the two runs —nvidia/nemotron-3-super-120b-a12bregisters 1000000 against a primary provider offering 262144,minimax/minimax-m31048576 against 524288, and both come back correct on this branch. Completion tokens are deliberately left untouched: enumerating the key union of every row in that same live response shows no top-levelmax_completion_tokensormax_output_tokensanywhere, so copying the sibling fix's fallback chain would have encoded a field the data never carries — which is why the production delta is 6 lines added and 1 removed against 53 lines of tests. The added coverage is shown load-bearing by reverting the production file tomainwhile keeping the new tests, which fails the precedence case with 1048576 where 524288 is expected (PR #118868)
n8n-io/n8n
· 2 merged · both released
- Every Salesforce Case given a Parent ID still landed with
ParentId: null, and the node reported success — the field is declaredParentIdin the node description, but both the create and update handlers read the lowercaseparentIdoff the collection, so the key was alwaysundefinedand the parent was never put on the request. Nothing surfaced the loss: Salesforce was simply never told. Reading the correctly-cased key restores it with no migration, since saved workflows already store the value underParentId— and the two existing tests that had mirrored the buggy lowercase key were corrected alongside new regression tests pinning create and update (PR #33775, shipped in n8n@2.32.0) - An AI agent whose HTTP tool call failed was told the status code and nothing else — never the server's own explanation, so a 403 carrying
{"error":"insufficient_scope","required":"read:users"}reached the model as a bare "Forbidden" and it retried blind instead of correcting the request. The tool built its response fromhttpCodepluserror.message, which left the branch that returns the body unreachable. Confirmed against a realNodeApiErrorassembled from an axios-shaped 403:causeandresponseboth come backundefinedwhile the body sits untouched oncontext.data— the payload was present the whole time, just never forwarded. The body now reaches the model, bounded on every axis that could turn a failure into a worse one: truncated so a long error can't eat the context window, binary and empty payloads skipped, credential-shaped values masked with the redaction patterns n8n already applies to skill tool output rather than a scheme invented for this path, and a serializer that cannot itself throw while an error is being handled (PR #34509, shipped in n8n@2.34.0)
huggingface/transformers
· 3 merged
- Anyone fine-tuning GIT since v4.49.0 trained it to predict two tokens ahead —
GitForCausalLMshifted its labels by hand and then passed them positionally, soshift_labelsstayedNoneand the loss helper shifted a second time; because the manual shift flattened to 1-D first, the pad-and-slice kept shapes consistent (N → N+1 → N), nothing raised, and each row's final target was silently pulled in from the next row in the batch. GIT can't simply drop the manual shift the way the earlier Moonshine fix did — its logits carry leading image positions that must be sliced regardless — soshift_labelsis passed explicitly, on 2-D tensors, which also removes the cross-row leak. Loss went from4.5848(matching the double shift) to4.6461, exactly the aligned cross-entropy (PR #47395) - Seven multimodal models raised outright under mixed precision —
Trainer(bf16=True), or any Accelerate autocast context — because they moved the encoder output to the text stream's device beforemasked_scatterbut not to its dtype.nn.Embeddingis not on autocast's cast list, soinputs_embedsstays float32 while the encoder's finalnn.Linearreturns bfloat16, andmasked_scatterrequires both operands to share a dtype: under the ordinary mixed-precision setup, not an edge case. Rather than guess the blast radius from model names, I listed all 121masked_scattercall sites undermodels/— 104 already align the dtype, 17 do not — and put every one of the 17 through the same autocast forward, built from the model's ownModelTesterwith no pretrained weights: seven raised, three were already safe because their scattered tensor comes from an embedding table in the same module, and the rest scatter structurally different things. The PR covers exactly the seven that raised, with the excluded sites tabulated in the body and the already-fixed Gemma 4 path kept as a control;pi0is a vision model, so the "audio family" cut I started with would have missed it (PR #47673) - Two more models raised under the same mixed precision setup, and the sweep that fixed those seven could not have reached them —
kosmos2andkosmos2_5move the vision features to the text stream's device before merging them intoinputs_embeds, but not to its dtype, so underTrainer(bf16=True)or any Accelerate autocast context the merge dies withIndex put requires the source and destination dtypes match. Same root cause as #47673, different operator: that PR's scope came from enumerating everymasked_scattercall site, and these two models merge with an advanced index assignment, which lowers toindex_put_— just as unable to type promote, and outside that enumeration by construction. Neither has amodular_*.pyeither, so nothing propagated into them from a sibling. The fix is the oneidefics2,idefics3andmodernvbertalready apply at the identical merge point, and both models were put through a bfloat16 autocast forward built from their ownModelTester, raising before and clean after.smolvlmcarries the same defect, but #41485 is already open against that file, so it is named in the PR body and deliberately left to that author instead of being duplicated (PR #47691)
koala73/worldmonitor
· 2 merged · 1 prototype
- Most of the dashboard's cross-source intelligence signals could not fire at all, and the seeder reported success on every run — the Railway seeder behind
intelligence:cross-source-signals:v1bare-JSON.parsed each of its Redis inputs, so every contract-mode key reached its extractor as the{ _seed, data }envelope it is stored in: the payload array wasundefined, theArray.isArrayguard on the next line was false, and the extractor returned[]without throwing — so the aggregator'stry/catchhad nothing to log and the run still exited 0, publishing a shorter list. Auditing all 21 extractors against the writer of each key exposed a second, independent layer: most also read field names and enum spellings their writer has never published — the wildfire signal filtered onradiativePower > 5000 || severity === 'extreme'against detections that carryfrpin MW and noseverityfield at all, leavingbrightness > 400as its only ever-correct clause, which is why the code reads plausibly and produces nothing. Neither existing suite could see any of this: both reconstructed the module withreadFileSync+ regex +vm, and one of those regexes deleted the reader outright. All 23 corrections are mutation-proven — reverting any single one to the code it replaced turns the suite red, no survivors (PR #5896) - A model ID the provider does not serve cost a wasted round-trip on every single call, indefinitely — the health gate probed
new URL(apiUrl).originwith a bare GET and read any HTTP response as healthy, so it never sawcreds.model: the request was built, rejected withhttp_4xx, and fell through to the next provider, with nothing in the logs pointing at the model as the cause. The evidence was already being collected and discarded — both provider loops read the error body for diagnostics and log the model beside it. Feeding that back into the gate quarantines theorigin|modelpair for 10 minutes after two consecutive rejections whose body explicitly names the model, at no new network cost. Detection is deliberately narrow: 401/403/429/5xx are credentials, rate limits and outages — provider-wide and silent about the model ID — so they never quarantine, and an unreadable body keeps the previous behaviour, making the fail-safe the status quo rather than a wrongly quarantined model. Pinned by a behavioural test that sends four calls at a dead model: 4 attempts againstmain, 2 here (PR #5458) - Designed and prototyped the client-side RAG pipeline that gave AI intelligence briefs historical context — embeddings and cosine similarity running in a Web Worker over an IndexedDB vector store, so retrieval needs no server-side index. My prototype (PR #647) was reworked by the maintainer and shipped as PR #675
agentscope-ai/QwenPaw
· 4 merged
- The headless
qwenpaw taskcommand could not run a single task, and reported its own failure as the task's —_run_taskpassed a barestrasMsg(content=...), but the pinnedagentscope==2.0.4.post1declaresMsg.contentaslist[ContentBlock]with nomode="before"validator, so pydantic raisedValidationErroron every invocation, before the agent was ever built. The call sits inside a broadexcept Exceptionthat turns any throw into{"status": "error", "error": ...}, which is exactly why this survived unnoticed: the symptom reads as a task that failed, not as a CLI that cannot construct its own input, and the error string it returns is a pydantic validation message about a type the user never chose. The repository had already settled the correct shape — every otherMsg(...)undersrc/qwenpawwraps its content in a block list, and the same function builds the right thing 25 lines earlier forAgentRequest— so the fix adopts agentscope's ownUserMsgfactory rather than hand-assembling aTextBlock, and the PR argues "make this agree with the rest of the repo" instead of asking for trust. Nothing caught it because all 15 tests intest_cli_task.pymonkeypatch_run_taskwholesale, leaving the function at zero coverage; the two sibling occurrences inproactive_responder.pyare named in the PR body as an offered follow-up rather than bundled in, since this repository rejects one fix spread across files. Merged unchanged, 2 source lines against 51 lines of new coverage (PR #6616) - Stopping a local model server on Windows could hang shutdown indefinitely, flash a console window on every poll, and crash outright on a non-UTF-8 console —
_is_pid_running()shells out totaskliston each iteration of the shutdown wait loop'ssleep(0.1), and it was the one call site in its own module that skipped thetimeout, thewindows_hidden_subprocess_kwargs()the module already defines, anderrors=on a locale-decoded read, so a cp936/GBK console raisedUnicodeDecodeErrorstraight out of the shutdown path. Measured on Windows 11 each probe costs ~0.157s, so the intended 0.1s poll actually ran at ~0.257s and a 5s graceful shutdown spawned up to 19tasklistprocesses. Two rounds of maintainer review moved the PR past that surface fix into the two real defects underneath: a failed probe returnedFalse, which callers read as a confirmed exit — so a timed-out probe madeshutdown_process_sync()report a graceful exit and skipkill()for a process still alive — and_PID_PROBE_TIMEOUTwas independent of the caller's deadline while the probe ran before the remaining budget was checked, so a 6s shutdown budget measured 20s in the worst case. Probe failures now assume the process is alive, the wait loop bounds each probe by what is left of its deadline, and the post-deadline path does the free localis_alive()check instead of spawning anothertasklist, letting the caller escalate. The elapsed-budget regression test runs under a virtual clock, so it asserts the 6.0s bound exactly without sleeping in CI (PR #6203) - A shutdown during boot could wipe every recorded day of token usage, silently — cancel the consumer while it is still reading the file (Ctrl-C,
uvicorn --reload, a quick restart) andstop()force-flushes a cache that was never seeded, committing{}overtoken_usage.jsonthrough an atomicos.replace()with no backup and nothing logged. The window only opens for users who have history to lose. Pinned by a regression test and a positive control (PR #6220) - Cut one of three
nvidia-smispawns at startup and half of those per/modelsrequest — 40% off the measured probe time: a CUDA guard re-ran a query that already returns cleanly without a driver (PR #6204)
MadsLorentzen/ai-job-search
· 2 merged
- Hex-encoded accents leaked into the LinkedIn scraper's CLI output as raw entities, and emoji came out mangled in every form — the decoder handled decimal entities only, and
String.fromCharCodetruncated supplementary-plane code points to 16 bits. 1 of 6 fixture cases passed before, 6 of 6 after, under network-free unit tests (PR #55) - Same bug in both duplicated decoders of the Jobindex scraper, where it matters more: on a Danish portal
æ/ø/åfrequently arrive as numeric entities, and their hex forms rendered broken (PR #56)
OthmanAdi/planning-with-files
· 3 merged
- Gave the project its first automated test run: CI until then only reviewed skill prose, never behavior — now pytest across Ubuntu and Windows plus vitest for the Pi extension, on every PR and push to master (PR #199)
- Running that suite on hosted runners exposed two latent cross-platform test failures — a Git Bash path-alias mismatch on Windows and Windows-shaped sanitizer vectors executing on POSIX. Fixed test-side, no production changes, and landed first so the CI PR could go green (PR #198)
- Made those runs reproducible: committed a lockfile for the Pi extension and switched the vitest job to
npm ci(PR #200)
openclaw/fs-safe
· 4 merged · 1 superseded
- A file outside a confined root was reported as inside it whenever the root string carried surrounding whitespace —
isPathInside("C:\root ", "C:\root\secret.txt")returnedtrue.isPathInsideis the predicate the other guards build on, and onwin32its only normalization step ended by delegating to a free-text string coercion helper — the same module that normalizes fast-mode flags and thread values — whose chain callsvalue.trim(). So a path used for containment math was trimmed before it was lowercased, and since whitespace is a legal part of a Windows path component, two genuinely different directories collapsed onto one comparison key. Fixed by lowercasing in place instead of routing the path through that helper, leaving separator and extended-length handling untouched. Unicode case folding is deliberately left alone and the reason is written into the PR:toLowerCase()is not injective — on a Turkish-language Windows install"İstanbul"folds to a 9-code-point string that never round-trips — so moving to an ASCII-only or locale-invariant fold changes behavior for every non-ASCII path and reads as an owner decision rather than a bug fix. The review asked for the intended contract to be owner-approved; it turned out the repository had already written it down, in two committed tests carryingskipIf(skipOnWindows). Removing that skip made the measurement possible and showed the deny list onmainapplying to the wrong directory — the protected directory writable while its sibling was blocked. Verified on Windows 11 with Node 24.15.0 through the real exported functions, with the new tests proven load-bearing by stashing onlysrc/path.ts: 2 failed | 3 passed before, 5 passed after (PR #78) - A path spelled
C:secret.txtread and wrote a different file than the one it named — on Windows it aliased ontosecret.txtat the root of a confined store, so two distinct untrusted keys resolved to one file.path.win32.isAbsolute("C:secret.txt")returnsfalsefor the drive-relative spelling — no separator follows the colon — whilepath.resolve()still consumes the drive prefix, so every layer that screens for absolute paths waved it through and the prefix vanished one call later. The escape is not the interesting part; the aliasing is, because the guard that catches escapes (isPathInside) sees only the already-collapsed result and correctly reports the file as in-root. Review pushed the fix down two layers, and both times the reviewer's location was one level off from where the hole actually was: the file-store parser was never onRoot's path at all (assertValidRootRelativePath()was a NUL check), and thenreadAbsolute()/reader()turned out to resolve the raw input before validating it. Proven on Windows at each step rather than argued —root.read("C:secret.txt")returning the realsecret.txt, with a resolution table for the four spellings and alogs/2026-08-02T10:30:00Z.logcontrol proving the anchored pattern does not eat timestamped names. I argued against gating the guard onprocess.platform, on the grounds that a key valid on Linux must not become a boundary violation when a store moves between hosts, and that was kept. The maintainer narrowed the blast radius before landing: applied where a path is created or resolved — writes,mkdir,copyIn,resolve(), everyFileStorekey, and the destination ofmove()— but not to reads,stat,listor the source ofmove(), sincec:notes.txtis a legal POSIX filename and refusing to read back a file that already exists on disk is collateral, not containment (PR #85, landed as #97) - Concurrent lock acquisition failed intermittently on Windows against a lock file that no longer existed — and because the failure had been read as CI noise for four releases, the repository's own
mainhad been red since a dependency refresh, with a different concurrency test failing nearly every run. Windows denies access to a file whose directory entry is still being torn down, so a contendedacquireFileLock()gotEPERMon a name already gone;acquire()treated onlyEEXISTas contention, so the transient denial escaped from both the exclusive create and the holder's snapshot read. Instrumented at the moment of failure,lstatreportedENOENTand a zero-delay retry opened the file. The evidence had to be a distribution, not a green run — 8 failures in 85 runs before, 0 in 110 after — because a single pass proves nothing about a race. Two review rounds raised the same P1 and were right both times: scoping a retry to a code region keeps leaking, because the region always holds more than the operation you measured (first the caller'spayload()callback, then a parent-directory open hidden inside the native create). Retrying is not neutral when the retried block can re-run a caller's callback, so the predicate that finally held names the evidence instead —EPERMand the exact lock pathname — and anything unproven propagates. The maintainer closed the last gap himself: the Windows native binding reportedERROR_ACCESS_DENIEDas pathlessEACCES, so on packaged installs the predicate could never match, invisible here because every test in the file forces native mode off (PR #87, landed as #92) - Every
Root.remove()failure was reported as a containment violation — a missing file, a non-empty directory and a busy handle all threwpath-alias/ "path is not under root", so a consumer could not separate a routine filesystem outcome from a safety rejection, and downstream code inopenclawhad grown a helper purely to unwrap the real errno back out of the bogus one.not-found,not-emptyandnot-removablewere declared in the exported error union and promised in six documentation sites, but constructed nowhere insrc/: the remove path funnelled every non-FsSafeErrorthrough a normalizer whose default ispath-alias. Framing decided the PR — the documentation was already correct, which puts this on the contract-repair side of the line this repository merges on, rather than the contract-change side where its one rejected external PR sits. Review then found a genuine defect in my first fix: the errno mapping wrapped the whole fallback including the parent-directory guard, so a rawELOOPsurfaced asnot-removablewith nothing deleted; the second push splits the guard into its own stage so only the deletion syscalls are classified, and the guard fails closed. The maintainer added the half I had missed — all three codes were also absent fromOPERATIONAL_CODES, socategorizeFsSafeError()kept labelling themcategory: "policy", and fixing the code without the category would have delivered half the change (PR #84, landed as #93) - Diagnosed why two Windows permission tests kept timing out in CI, and flagged the unbounded subprocess call underneath it — the
Node N checkjob never builds the native binding, so those tests take the command fallback and pay six process spawns each, two per inspection (powershell.exefor the owner query,icacls.exefor the ACL). I opened the timeout increase as an explicitly test-only PR and kept the real finding out of it:defaultPermissionExeccalledexecFileAsyncwith notimeout, so a wedgedicacls.exewould hanginspectPathPermissions()indefinitely for any consumer — a public behaviour change that did not belong smuggled into a budget bump. The maintainer took the diagnosis and not the number, which was the better outcome: rather than widening the budget to fit the cost, PR #89 removes a redundant inspection so the affected tests drop to four spawns and ordinary Windows CI keeps exercising the command fallback, and it bounds the commands with a fail-closed result — an owner query that cannot complete now yieldssource: "unknown"andreadSecureFile()refuses withpermission-unverifiedinstead of returning a permissive answer. The tests went from 15-second timeouts to about four seconds (PR #88, superseded by #89)
openclaw/clawhub
· 3 merged
- A fully documented skill was rejected at publish as "too thin or templated" whenever its SKILL.md carried no YAML frontmatter and used
---as an ordinary Markdown horizontal rule — the skill never reached the catalog, and the newest accounts felt it first, since the reject floor is highest for the lowest trust tier. The quality gate stripped frontmatter with anm-flagged pattern, so^matched at every line start rather than only the start of the document: it latched onto the first thematic break and deleted everything up to the second. Frontmatter is optional on publish — the display name comes from the mutation's own arguments — so that document is legal input. 94% of the body was being discarded before measurement: a 109-word SKILL.md measured as 6 words,score30 → 100,decisionreject→pass; the truncated text also fed the template-spam fingerprint, so similarity was being compared over a fragment. Anchoring the pattern to the document start is what the canonical parser and the repo's three other frontmatter patterns already did, pinned by the first unit tests for a module that had none — two of the three fail on the parent commit. The maintainer's follow-up carried it further and routed the gate through that shared parser, deleting the fourth regex outright (PR #3297) - A publisher who swapped a file after the first changelog preview landed submitted a changelog describing a bundle they were no longer publishing — on the skill update form the generated "What changed" text is cached in a ref keyed on the slug, version, SKILL.md size and
lastModified, and the path count, while the path list itself is what the action receives asfilePaths: exchange one bundled script for another and the key is identical, so the second request is skipped. Nothing reset that key when the selection changed, and the generation effect returns early while the field is non-empty — so once a preview had landed, no later change to the file set could replace it for the rest of the session. The sibling plugin publish form already covered both halves, keying onnormalizedPaths.join("\0")and resetting the cached key when the file set changes; the skill form now agrees with it, and the reset returns early on a changelog the publisher typed, so manual text is never discarded. Read out of the mounted form with the sameSKILL.mdinstance reused so only a sibling file differs: 1 preview call carrying the supersededfilePathsbefore, 2 calls ending on the current bundle after (PR #3296) - A listing untouched for just under a year read "Updated 12mo ago" instead of "1y ago" across skill rows, browse results, plugin detail, the dashboard and the GitHub sync timestamp:
timeAgomeasures months as 30 days but years as 365, and 360–364 days still divides into twelve whole months while sitting below the year threshold. Aligned with the publisher-profile renderer by deriving years from whole months, removing the unit mismatch at its source — with the first tests for a file that had none despite being inside the coverageincludelist (PR #3174)
openclaw/mcporter
· 1 merged
- A server that was simply unreachable was reported as needing authorization — a browser OAuth flow launched at the user and the stored server definition promoted to
auth: 'oauth'— whenever the connection error's text happened to contain the digits401anywhere: a port, a timeout duration, a hostname, a request id. The classifier's auth check was a rawincludes('401')with no word boundary, and it ran before both the generic HTTP branch and the offline-transport branch, so a message that plainly matchedECONNREFUSEDstill came backauthand the real fault the user had to fix was hidden behind an authorization prompt. The control case is one character wide:ECONNREFUSED 127.0.0.1:9000classifiesoffline,127.0.0.1:14012classifiesauth— the committed offline test passed only because its port happened to be lucky. The intended precedence was already written down in the repository's own tests (code=404 as http (not auth),code=500 as http,405 as transport/http instead of auth) and in two earlier fixes pointing the same way; the implementation honoured it only while the message carried no auth-like text. Fixed as an ordering rather than a special case — a known status code decides first, then the unambiguous keyword signals (unauthorized,invalid_token,forbidden), thenOFFLINE_PATTERNS, and only then the bare401numeral, the one signal ambiguous enough to belong below transport evidence. That split is also what closed the review's remaining merge risk in code instead of asking for it to be accepted: demoting every auth signal below the offline check would have sent a genuineinvalid_tokenpayload that also says "connection timed out" tooffline, so only the numeral moved, and the compatibility change is confined to exactly the reported defect. The word boundary itself took two passes — excluding neighbouring digits still letrequest_401_idandabc401defthrough, while\bwould have brokenunauthorized_client, the RFC 6749 error code — so the numeral is bounded on alphanumerics and_while the keywords stay substrings. Proven through the realisUnauthorizedError→maybeEnableOAuthpath with no mocks, no injected transports and no network: three transport failures that promote tooauthonmainstay unpromoted here, with the lucky-port case as the control, and every added regression shown load-bearing by restoring the previous ordering and watching exactly the intended tests go red and no others. The maintainer merged it with the ambiguous-numeral precedence accepted as implemented (PR #248)






