# I spent one day smashing three real open source bugs

> Source: <https://dev.to/aniruddhaadak/i-spent-one-day-smashing-three-real-open-source-bugs-4b74>
> Published: 2026-08-23 15:54:30+00:00

*This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.*

When the Summer Bug Smash challenge started, I gave myself one rule. Every fix has to be real, and every fix has to carry its own test that fails before the change and passes after it. I gave myself one day to see how far that rule could take me. It took me through three open source projects, three pull requests, and a lot of green check marks. Here is how it went, bug by bug.

My first stop was OpenClaw, a personal AI assistant project written in TypeScript. Issue 119360 described something spooky. When you changed a setting in the UI and then pressed cancel, the gateway still restarted. Cancel was supposed to mean do nothing, yet the process bounced anyway, dropping every active session on the floor.

Reading the code made the ghost visible. The restart planner compared your current config against a baseline it called the runtime compare config. That baseline was supposed to represent what the gateway was actually running. But it got updated during planning, before any reload was accepted. So when you cancelled, the planner compared your config against a baseline that had already drifted toward it, saw a mismatch, and ordered a restart for changes you had just taken back.

The fix keeps planning pinned to the config the user actually accepted, and lets the runtime baseline move only when a reload truly goes through. On top of that I added a narrow gate so a plain revert cancels cleanly instead of scheduling a pointless bounce. The hard part was scope. A careless gate here can swallow real restarts, like plugin or MCP runtime changes, which must always trigger one. The final condition only fires when there are config level diffs, no plugin reload is planned, no MCP disposal is planned, and the runtime diff is empty.

Fixes #119360

A transient write to a restart-required config path (e.g. `gateway.tools.allow`

) followed by a same-session revert that leaves `openclaw.json`

byte-identical to the pre-change file still schedules and performs a full Gateway restart (SIGUSR1 after drain). Restart debt latches onto the first restart-required candidate and survives superseding writes, so operators who probe a setting and immediately undo it still lose Control UI / channel sessions for a full restart cycle.

The reloader plans every candidate against a single acceptance baseline (`currentCompareConfig`

). Once restart-required candidate B is observed and accepted, that baseline becomes B — so when the file later settles back to the original running bytes A, the planner diffs A against **B**, still sees protected-path changes, and re-arms the deferred restart even though nothing differs from what the process is actually running.

Planning continues to use the acceptance baseline untouched (hot/no-op classification, runtime-overlay handling, and managed-restart choreography depend on it). The change adds a separate *runtime* reference — what this process has actually adopted — and uses it for exactly one decision:

`currentRuntimeCompareConfig`

advances whenever the process adopts a snapshot (`markRuntimeCommitted`

, normal `commitReloadBaseline`

completion) but deliberately `followUp.requiresRestart`

and `plan.restartGateway`

), before arming via `prepareRestart`

, an exact revert check runs: if the candidate deep-equals `currentRuntimeCompareConfig`

, the deferred restart is cancelled (logged, committed as baseline, no SIGUSR1).This keeps every existing planning behaviour intact — candidates on top of a deferred config still hot-reload against the deferred baseline and still revalidate pending restart secrets — while an exact revert-to-running can no longer re-arm debt. Plugin reload/MCP-dispose plans are exempt from the cancel so bundled-plugin work is never dropped by the shortcut.

New regression test in `src/gateway/config-reload.test.ts`

— "does not re-request a restart when a deferred config reverts to the runtime baseline":

`onRestart`

fires once.`onRestart`

is still exactly once, with no `onHotReload`

and no `onNoopConfigCommit`

— the revert is absorbed without arming anything.Matching handler-surface coverage added in `src/gateway/server-reload-handlers.test.ts`

("cancels a deferred restart when config returns to the running baseline").

Verification trail during development:

`plans one immutable runtime override snapshot per candidate`

caught that this starves no-op classification of runtime-overlay reversals (reproduced locally pre/post-fix), and the Gmail handler suite showed hot-reload sequences must keep planning against the acceptance baseline. The design was corrected accordingly: acceptance baseline drives planning; the runtime baseline only gates the restart-arm decision.The full CI matrix ended at 206 checks, all green, including two large test shards that stress exactly this path.

Second stop, Rocket.Chat's design system, Fuselage. Two related bugs lived in the Slider component's track fill. First, the colored fill between thumb and track start used left to right math only, so in right to left locales like Arabic or Hebrew the fill sat on the wrong side of the thumb. Second, if a slider had a minValue above zero, the fill ignored it and drew from the very start of the track, showing a range that did not match reality.

Both problems shared one root cause. The fill computed percentages by hand instead of asking the component state. My change makes it read the thumb percent straight from state, then flips the gradient direction based on locale direction. Writing the tests first paid off here. Three of them failed against the old code, and all eight passed after the fix.

Fixes the `Slider`

track fill rendering in two scenarios where it did not match the actual thumb position:

`minValue`

produced a wrong fill position`getThumbPosition`

computed `(value / (maxValue - minValue)) * 100`

, which ignores the offset of `minValue`

. For example, a slider with `minValue={50}`

`maxValue={150}`

and value `100`

rendered its fill at 100% instead of 50%.

The component now uses react-stately's own percent calculation (`state.getThumbPercent(index)`

), which also fixes multi-thumb sliders where each thumb can have a different range (previously both thumbs shared `getThumbMaxValue(1) || getThumbMaxValue(0)`

).

react-aria flips horizontal slider geometry in RTL locales, but the track gradient was hardcoded to `to right`

, so in RTL languages the filled portion appeared on the wrong side of the thumb. The gradient direction now follows `useLocale().direction`

.

Running the updated spec against the **old** `SliderTrack.tsx`

:

```
x should position the track fill relative to minValue
x should mirror the track fill direction in RTL locales
x should keep the multi-thumb band ordered in RTL locales
Tests:       3 failed, 5 passed, 8 total
```

With the fixed `SliderTrack.tsx`

:

```
PASS packages/fuselage/src/components/Slider/Slider.spec.tsx
Tests:       8 passed, 8 total
```

The vertical slider path is intentionally untouched: `to top`

+ percent-from-min was already correct for vertical orientation.

[@rocket](https://dev.to/rocket).chat/fuselage patch changeset included.

A changeset is included so the library gets a proper version bump when the maintainers merge.

Third stop, npmx.dev, a package registry that renders API docs for npm packages. Its documentation engine formats types coming out of deno doc JSON. Issue 3154 reported that many modern TypeScript types rendered as unknown[unknown]. Intersections, tuples, conditional types, mapped types, imported types, type predicates, typeof queries, infer positions, rest and optional members, even parenthesized groups all fell into a default branch that printed that sad pair of words.

I added eleven small formatters, one per kind, plus proper handling for bigInt literals and template literals while I was in there. Each formatter follows the existing style in the file, and the shared type definitions were extended to match what deno doc can actually emit. Eight regression tests now cover the exact shapes from the issue, and the whole unit suite for the formatter module passes.

Resolves: #3154

On package docs pages, exported symbols whose types are built from intersection / conditional / mapped / tuple constructs rendered as the literal string `unknown`

— e.g. [https://npmx.dev/package-docs/trslate/v/1.6.4](https://npmx.dev/package-docs/trslate/v/1.6.4) showed:

```
constructor(schema: T, arg_1: unknown)
type SKey<T> = unknown[unknown]
```

even though the package's `index.d.ts`

contains proper types.

`formatType()`

in `server/utils/docs/format.ts`

implemented recursive formatters for only 10 of the ~21 `TsType`

kinds that `@deno/doc@0.189.1`

emits. Unhandled kinds fell back to `type.repr`

, but deno_doc returns an **empty string** for `repr`

on structured types (intersections, mapped/conditional types, type literals — visible in our own fixtures), so the final fallback produced `unknown`

.

This is the same failure mode previously fixed for #1411 by adding formatters (`fnOrConstructor`

, `typeLiteral`

, `indexedAccess`

, `typeOperator`

) — this PR extends the same pattern to the remaining kinds.

`intersection`

, `tuple`

, `parenthesized`

, `rest`

, `optional`

, `typeQuery`

, `conditional`

, `mapped`

, `importType`

, `infer`

, `typePredicate`

.`bigInt`

and template literals.`(A | B)[]`

instead of invalid `A | B[]`

); intersections parenthesize union/conditional/function members.`TsType`

interface in `shared/types/deno-doc.ts`

with the missing fields, matching the flat serde shape of @deno/doc 0.189.1 (`js/types.d.ts`

).With the fix, the trslate signatures render as:

```
constructor(schema: T, args: K & T[length]): Translation<T>
```

Added a regression suite in `test/unit/server/utils/docs/format.spec.ts`

mirroring trslate's real d.ts shapes:

```
Test Files  1 passed (1)
     Tests  14 passed (14)
```

(8 new tests covering intersections, conditionals, mapped types, indexed access over mapped/intersection members, tuples + rest, typeof queries, import types, infer, type predicates, bigInt/template literals, and an end-to-end function signature.)

Three things stuck with me. First, a tiny reproduction is worth an hour of staring. Every one of these bugs became obvious the moment I could trigger it on demand. Second, tests that fail first are the cheapest proof that a fix does something. They also protect the next person who touches the code. Third, reading the surrounding code before writing anything saves more time than it costs. Every repo already had conventions for tests, changesets, and commit style, and following them made review easy for everyone.

Three projects, three fixes, each shipped with failing first tests and green CI. The challenge closes on August 24, so if you have been waiting for a reason to send your first bug fix, this is a good one. Pick an issue that annoys you, shrink it until it fits on one screen, and make the test prove you killed it.
