Skip to content
View Yigtwxx's full-sized avatar

Block or report Yigtwxx

Block user

Prevent this user from interacting with your repositories and sending you notifications. Learn more about blocking users.

You must be logged in to block users.

Maximum 250 characters. Please don’t include any personal information such as legal names or email addresses. Markdown is supported. This note will only be visible to you.
Report abuse

Contact GitHub support about this user’s behavior. Learn more about reporting abuse.

Report abuse
Yigtwxx/README.md

Yiğit Erdoğan

AI Engineer · I build RAG & LLM systems end to end — prototype to production

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.

EmailredditLinkedInXInstagramYouTubedev.toycombinatorDownload CV

What I'm up to

  • 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

Tech Stack

AI / MLPyTorchHugging FaceLangChainOllamaAnthropicGeminiGroq
RAG / DataChromaQdrantPostgreSQLSupabaseMongoDBSQLiteRedisNumPypandas
BackendPythonFastAPIPydanticSQLAlchemypytest
FrontendTypeScriptNext.jsReactTailwind
DevOpsDockerGitHub ActionsCaddyNginxSentry
SecurityCodeQLTrivyDependabot

Open Source Contributions

openclaw/openclawstars · 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 .cmd launcher was written in UTF-8, but cmd.exe parses 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 from MODULE_NOT_FOUND to 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.md came 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 stop reason marking it successful, so nothing downstream could retry or warn and per-session accounting drifted low. Any answer hitting the output cap ends incomplete, 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 try whose catch treats 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 valid Retry-After sitting 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 codesnpm install, a coloured test run, a docker 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 local exec runtime 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: 0 while 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 while input absorbed the writes — so both land together, proven over a real socket with a real transport: 80/070/10 tokens, and a real overflow that the half-fix reports as false (PR #111435)
  • A browser that never came back permanently bricked tab tracking for its profile — from then on every browser open opened 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-row reject-new cap. 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, with now the only injected input (PR #111307)
  • Host execution blocked CC, CPP and CXX as compiler selectors but still accepted CXXCPP — GNU Autoconf's C++ preprocessor selector, the exact counterpart of the CPP that 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 requested CXXCPP both become null, the requested one is reported in rejectedOverrideBlockedKeys, benign controls survive and the child exits 0 with no CXXCPP in its environment (PR #112684)
  • The edit tool 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 larger oldText that 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, $defs and six more survived — and additionalProperties holding 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, no fetch stub: additionalProperties arrives as {"maxLength":100,"type":"string"} on main and {"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 data payload and every raw line. The answer already existed one directory over: the canonical Anthropic transport converts only a SyntaxError into a shared malformed-fragment marker and keeps the original error as cause, 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 structured error body 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 only JSON.parse's SyntaxError, convert it to the shared marker, keep the original as cause, and yield outside the catch so a SyntaxError injected by a consumer through iterator.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-length and throws before it ever reaches response.body.getReader(), so the finally released 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 real release, with the wrapper only recording what release() observes before delegating: bodyUsed goes from false to true on 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 convertMessages flattened a multi-block assistant turn with join(""). 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 a thinking block into a text block 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+json payload, 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 a Response.clone() tee, and cancelling such a branch never settles while its sibling is live. Proven against a real node:http server that answers 200 and then never ends the body, driven through the real global fetch and 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 to Buffer[ 123, 34, 101, 114, 114, … ] and Buffer[ 60, 104, 116, 109, 108, … ], which are {"err and <html handed 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_length is the catalog-wide ceiling across every routing candidate, while top_provider.context_length describes 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.ts prefers the primary provider and falls back to the catalog-wide value — and the same normalization had just landed in src/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/models answers with no key and no inference call, so the real 346-row response was fed through the actual discoverKilocodeModels() implementation with the production file as the only variable between the two runs — nvidia/nemotron-3-super-120b-a12b registers 1000000 against a primary provider offering 262144, minimax/minimax-m3 1048576 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-level max_completion_tokens or max_output_tokens anywhere, 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 to main while keeping the new tests, which fails the precedence case with 1048576 where 524288 is expected (PR #118868)
n8n-io/n8nstars · 2 merged · both released
  • Every Salesforce Case given a Parent ID still landed with ParentId: null, and the node reported success — the field is declared ParentId in the node description, but both the create and update handlers read the lowercase parentId off the collection, so the key was always undefined and 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 under ParentId — 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 from httpCode plus error.message, which left the branch that returns the body unreachable. Confirmed against a real NodeApiError assembled from an axios-shaped 403: cause and response both come back undefined while the body sits untouched on context.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/transformersstars · 3 merged
  • Anyone fine-tuning GIT since v4.49.0 trained it to predict two tokens aheadGitForCausalLM shifted its labels by hand and then passed them positionally, so shift_labels stayed None and 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 — so shift_labels is passed explicitly, on 2-D tensors, which also removes the cross-row leak. Loss went from 4.5848 (matching the double shift) to 4.6461, exactly the aligned cross-entropy (PR #47395)
  • Seven multimodal models raised outright under mixed precisionTrainer(bf16=True), or any Accelerate autocast context — because they moved the encoder output to the text stream's device before masked_scatter but not to its dtype. nn.Embedding is not on autocast's cast list, so inputs_embeds stays float32 while the encoder's final nn.Linear returns bfloat16, and masked_scatter requires 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 121 masked_scatter call sites under models/ — 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 own ModelTester with 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; pi0 is 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 themkosmos2 and kosmos2_5 move the vision features to the text stream's device before merging them into inputs_embeds, but not to its dtype, so under Trainer(bf16=True) or any Accelerate autocast context the merge dies with Index put requires the source and destination dtypes match. Same root cause as #47673, different operator: that PR's scope came from enumerating every masked_scatter call site, and these two models merge with an advanced index assignment, which lowers to index_put_ — just as unable to type promote, and outside that enumeration by construction. Neither has a modular_*.py either, so nothing propagated into them from a sibling. The fix is the one idefics2, idefics3 and modernvbert already apply at the identical merge point, and both models were put through a bfloat16 autocast forward built from their own ModelTester, raising before and clean after. smolvlm carries 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/worldmonitorstars · 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:v1 bare-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 was undefined, the Array.isArray guard on the next line was false, and the extractor returned [] without throwing — so the aggregator's try/catch had 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 on radiativePower > 5000 || severity === 'extreme' against detections that carry frp in MW and no severity field at all, leaving brightness > 400 as 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 with readFileSync + 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).origin with a bare GET and read any HTTP response as healthy, so it never saw creds.model: the request was built, rejected with http_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 the origin|model pair 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 against main, 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/QwenPawstars · 4 merged
  • The headless qwenpaw task command could not run a single task, and reported its own failure as the task's_run_task passed a bare str as Msg(content=...), but the pinned agentscope==2.0.4.post1 declares Msg.content as list[ContentBlock] with no mode="before" validator, so pydantic raised ValidationError on every invocation, before the agent was ever built. The call sits inside a broad except Exception that 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 other Msg(...) under src/qwenpaw wraps its content in a block list, and the same function builds the right thing 25 lines earlier for AgentRequest — so the fix adopts agentscope's own UserMsg factory rather than hand-assembling a TextBlock, 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 in test_cli_task.py monkeypatch _run_task wholesale, leaving the function at zero coverage; the two sibling occurrences in proactive_responder.py are 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 to tasklist on each iteration of the shutdown wait loop's sleep(0.1), and it was the one call site in its own module that skipped the timeout, the windows_hidden_subprocess_kwargs() the module already defines, and errors= on a locale-decoded read, so a cp936/GBK console raised UnicodeDecodeError straight 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 19 tasklist processes. Two rounds of maintainer review moved the PR past that surface fix into the two real defects underneath: a failed probe returned False, which callers read as a confirmed exit — so a timed-out probe made shutdown_process_sync() report a graceful exit and skip kill() for a process still alive — and _PID_PROBE_TIMEOUT was 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 local is_alive() check instead of spawning another tasklist, 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) and stop() force-flushes a cache that was never seeded, committing {} over token_usage.json through an atomic os.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-smi spawns at startup and half of those per /models request — 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-searchstars · 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.fromCharCode truncated 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-filesstars · 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-safestars · 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") returned true. isPathInside is the predicate the other guards build on, and on win32 its 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 calls value.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 carrying skipIf(skipOnWindows). Removing that skip made the measurement possible and showed the deny list on main applying 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 only src/path.ts: 2 failed | 3 passed before, 5 passed after (PR #78)
  • A path spelled C:secret.txt read and wrote a different file than the one it named — on Windows it aliased onto secret.txt at the root of a confined store, so two distinct untrusted keys resolved to one file. path.win32.isAbsolute("C:secret.txt") returns false for the drive-relative spelling — no separator follows the colon — while path.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 on Root's path at all (assertValidRootRelativePath() was a NUL check), and then readAbsolute()/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 real secret.txt, with a resolution table for the four spellings and a logs/2026-08-02T10:30:00Z.log control proving the anchored pattern does not eat timestamped names. I argued against gating the guard on process.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(), every FileStore key, and the destination of move() — but not to reads, stat, list or the source of move(), since c:notes.txt is 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 main had 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 contended acquireFileLock() got EPERM on a name already gone; acquire() treated only EEXIST as contention, so the transient denial escaped from both the exclusive create and the holder's snapshot read. Instrumented at the moment of failure, lstat reported ENOENT and 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's payload() 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 reported ERROR_ACCESS_DENIED as pathless EACCES, 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 threw path-alias / "path is not under root", so a consumer could not separate a routine filesystem outcome from a safety rejection, and downstream code in openclaw had grown a helper purely to unwrap the real errno back out of the bogus one. not-found, not-empty and not-removable were declared in the exported error union and promised in six documentation sites, but constructed nowhere in src/: the remove path funnelled every non-FsSafeError through a normalizer whose default is path-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 raw ELOOP surfaced as not-removable with 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 from OPERATIONAL_CODES, so categorizeFsSafeError() kept labelling them category: "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 check job never builds the native binding, so those tests take the command fallback and pay six process spawns each, two per inspection (powershell.exe for the owner query, icacls.exe for the ACL). I opened the timeout increase as an explicitly test-only PR and kept the real finding out of it: defaultPermissionExec called execFileAsync with no timeout, so a wedged icacls.exe would hang inspectPathPermissions() 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 yields source: "unknown" and readSecureFile() refuses with permission-unverified instead of returning a permissive answer. The tests went from 15-second timeouts to about four seconds (PR #88, superseded by #89)
openclaw/clawhubstars · 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 an m-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, score 30 → 100, decisionrejectpass; 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 as filePaths: 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 on normalizedPaths.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 same SKILL.md instance reused so only a sibling file differs: 1 preview call carrying the superseded filePaths before, 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: timeAgo measures 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 coverage include list (PR #3174)
openclaw/mcporterstars · 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 digits 401 anywhere: a port, a timeout duration, a hostname, a request id. The classifier's auth check was a raw includes('401') with no word boundary, and it ran before both the generic HTTP branch and the offline-transport branch, so a message that plainly matched ECONNREFUSED still came back auth and 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:9000 classifies offline, 127.0.0.1:14012 classifies auth — 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), then OFFLINE_PATTERNS, and only then the bare 401 numeral, 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 genuine invalid_token payload that also says "connection timed out" to offline, 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 let request_401_id and abc401def through, while \b would have broken unauthorized_client, the RFC 6749 error code — so the numeral is bounded on alphanumerics and _ while the keywords stay substrings. Proven through the real isUnauthorizedErrormaybeEnableOAuth path with no mocks, no injected transports and no network: three transport failures that promote to oauth on main stay 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)

My Contributions

Snake animation

GitHub Stats

GitHub StatsMost Used Languages

Pinned Loading

  1. MaestroMaestroPublic

    Orchestrate armies of AI agents from a single prompt. Open-core, bring-your-own-key, and self-hostable — Orchestrator routes, specialists execute, a reviewer keeps them honest.

    Python 1

  2. awesome-rag-productionawesome-rag-productionPublic

    A curated list of battle-tested tools, frameworks, and best practices for building scalable, production-grade Retrieval-Augmented Generation (RAG) systems.

    Python 184 46

  3. OracleXOracleXPublic

    AI-driven financial terminal that runs locally on your machine. Analyzes market news using Llama 3.1 to provide institutional-grade trading signals, technical analysis, and sentiment scoring for Cr…

    Python 1 2

  4. J.A.R.V.I.SJ.A.R.V.I.SPublic

    J.A.R.V.I.S: An AI-powered Open Source Intelligence (OSINT) system. It orchestrates deep web scraping and local LLMs to autonomously generate comprehensive intelligence dossiers.

    Python 7 2

  5. awesome-claude-multi-agentawesome-claude-multi-agentPublic

    A curated list of frameworks, patterns, and research for orchestrating multi-agent systems built on Claude, including subagent collections and primary sources from Anthropic.

    Shell 3 2

  6. dl_xview_yolodl_xview_yoloPublic

    YOLOv8 implementation for remote sensing and satellite image analysis. Features custom tiling and inference pipelines for xView & DOTA datasets.

    Python 10 3