Ноль, который списывал всё
Medusa: кнопка «убрать скидку» выдавала максимальную, потому что ноль в JavaScript ложный
miki/nol-kotoryy-spisyval-vse · v2
Medusa: кнопка «убрать скидку» выдавала максимальную, потому что ноль в JavaScript ложный
Medusa, плагин лояльности, август 2026.
В магазине есть накопленные баллы — «складской кредит». Их можно частично потратить на заказ. Шаг, который этим занимается, документирован просто: передайте сумму — он уберёт прежнее списание с корзины и создаст новое на эту сумму.
А чтобы вообще убрать списание, передайте ноль.
Читается как «если сумму передали — берём её, иначе весь остаток». И это верно ровно до того дня, когда кто-то передаст ноль.
Ноль в JavaScript ложный. Проверка решает, что суммы не было, и подставляет balance — весь остаток покупателя. Из описания исправления #16378:
Clearing the store credit from a cart left it with a credit line for everything the customer had.
Очистка списания с корзины оставляла её со списанием на всё, что у покупателя было.
Кнопка «убрать скидку» выдавала максимальную скидку. Механика, за которую в другом месте платят маркетологам.
9 августа 2026, 14:24. giladv заводит #16377 — и не просто жалуется, а приносит причину и правку в самой задаче:
Because
0is falsy, it is treated as an omitted amount and the workflow falls back tostoreCreditAccount.balance. … Use an undefined check rather than truthiness, so0remains a valid specified amount.
Через три минуты отвечает medusa-os-bot — это Claude, запущенный из GitHub Actions (см. карточку «Medusa: повадки проекта»), — и подтверждает: файл, строка 166, «Fix is straightforward and suitable for a community contribution». Ставит type: bug и good first issue.
Ещё через тринадцать минут fzlzjerry пишет «I would like to work on this» и расписывает план. PR от него так и не пришёл — план исполнили другие.
Самое поучительное здесь не сама ошибка, а то, где нашлось лекарство. Первая правка — #16378, lazerg, 14:58, через 34 минуты после задачи:
Changed the guard to
isDefined(input.amount), which is the same check the balance validation a few lines below already uses.
…ровно та проверка, которую проверка остатка несколькими строками ниже уже использует.
И правда: на develop сразу под строкой 166 стоит if (isDefined(input.amount) && …). Верный образец лежал в том же файле, в пределах экрана. Просто в одном месте написали ?, а в другом — isDefined.
Вторая правка — #16379, 15:26, — говорит то же другими словами:
It updates the fallback condition in
computeCreditLineActionsStepto strictly check if the value is defined, rather than relying on a truthiness check.
Здесь есть деталь про стиль. Правила проекта для бота начинаются с «No semicolons», а в этом файле на develop тридцать три строки кончаются точкой с запятой. Автор #16379 заодно убрал их все — диф вышел +35/−38 при исправлении в одно слово; бот отметил: «correct per Medusa convention but unrelated to the bug fix», и попросил только changeset.
Автор #16379 закрыл его сам через 51 минуту. Сегодня GitHub показывает вместо его имени ghost — аккаунт удалён.
Ошибку чинили трижды за три дня: #16378 и #16379 — 9 августа, #16401 (ekley) — 11-го. Разные авторы, одна строка.
Это не про несогласованность, а про диагностичность: когда баг звучит как «я убираю списание, а он списывает всё», воспроизводится он с первой попытки и находится за минуты. Такие ошибки собирают очередь из желающих починить.
Что стало с очередью — на 02.09.2026:
- #16378 — бот одобрил через четыре минуты (
initial-approval). Дальше тишина до 31 августа, когда бот пометил PRStaleс текстом «open 60 days with no activity» (порог в конфиге — 15 дней). Через полчаса lazerg: «This has been sitting since Aug 9 with a green fix, a test, and no maintainer review yet… Could someone take a look? cc @NicolasGorga». - #16379 — закрыт автором в тот же день.
- #16401 — единственный из трёх, куда пришёл человек. Nicolas Gorga (NicolasGorga), 26 августа: «The update is correct, though would like to understand first what real use case does calling this workflow with amount = 0 cover». Вопрос без ответа.
Строка 166 на develop та же, что 9 августа. #16377 пометили Stale 31 августа вместе с PR.
Рядом в задаче — спор, который стоит прочитать целиком. 10 августа msaikens: «Using Codex to resolve issues is a great way to be more efficient but I strongly dislike AI as a "source of truth"». giladv в ответ: «the ease of AI driven tickets empowers us to document issues that might otherwise be slipping between the cracks. I think that's a fair tradeoff». Задача при этом — с точной причиной и верной правкой, и бот, который её подтвердил, — тоже ИИ.
В тот же день, 9 августа в 15:39, giladv дописывает в #16377 второй симптом: повторный вызов с большей суммой не срабатывает, корзина держит старую. Обходной путь — сначала вызвать с «маленькой истинной суммой, например 0.001», потом с нужной. Обход ошибки через ту же самую ошибку.
11 августа приходит #16405 (actualmayank). Удаление старой строки списания и создание новой были обёрнуты в parallelize(...) — выполнялись одновременно. Хотя документация того же шага говорит: «removes any existing store-credit lines and creates a new credit line» — то есть сначала одно, потом другое.
That mismatch is consistent with a cart intermittently keeping a stale credit amount when the workflow is called again with a new amount.
Это расхождение объясняет, почему корзина иногда сохраняла прежнюю сумму списания.
Расхождение между документацией шага и его реализацией прожило до первого плавающего бага. Плавающие баги на то и плавающие, что живут долго.
#16405 прошёл три круга бота за двадцать минут — changeset, формат changeset, отступы — и остался с requires-more из-за отступов: «Running Prettier on the file will correct this». parallelize( на develop на месте, строка 274.
- giladv, без имени в профиле. Завёл #16377 с причиной и правкой внутри, дописал второй симптом, отстоял задачи, написанные с ИИ.
- lazerg — Lazizbek Ergashev. #16378 через 34 минуты после задачи, с тестом; 31 августа напомнил о себе.
- Автор #16379 — аккаунт удалён, GitHub показывает
ghost. Заодно убрал точки с запятой; закрыл PR сам через 51 минуту. - ekley — Hossein Ekramian. #16401 — тот, что получил единственное ревью человека.
- actualmayank — mayank k. #16405:
parallelizeтам, где документация обещала «сначала удалить, потом создать». - fzlzjerry — Morax. «I would like to work on this» — с планом, без PR.
- msaikens — Mitchell Aikens. «I strongly dislike AI as a "source of truth"».
- NicolasGorga — Nicolas Gorga, MedusaJS. Единственное ревью человека на три PR: «what real use case…».
Подробнее о проекте и людях — в карточке «Medusa: повадки проекта».
Что с этим делать у себя
Ищите amount ?, price &&, count ? … : — всякое место, где число проверяется на истинность, а не на существование.
- Каждое найденное место либо исправлено, либо помечено как намеренное — с объяснением
- Проверены не только суммы: цена 0 (акция), количество 0, порог 0
Прежде чем придумывать свою — прочитайте соседние двадцать строк. В Medusa образец лежал в том же файле, в пределах экрана.
- В правленном файле осталась ОДНА форма проверки на существование
Если написано «сначала убрать, потом создать», а в коде оба действия идут параллельно — неправ код.
- Либо код приведён к описанию, либо описание исправлено — и сказано, кто из них врал
Related lists
Medusa: защита от повтора отказывает там, где можно было ответить, и пропускает там, где повтор стоит денег
Найти в открытом проекте настоящую историю поломки, восстановить ход мысли по написанному и закончить проверкой своего кода
k8sgpt отправляет поломки кластера языковой модели и обещает анонимизацию. Через три месяца пользователь спросил, где её границы, — и получил честный ответ, который держится третий год
Как проект живёт и принимает чужие правки, и кто в нём встречался в разборах — по поступкам, датам и ссылкам
Better Auth: ограничение частоты считалось от последнего запроса, включая отклонённые, — и разблокировка не наступала никогда
