Without this file, every new agent would keep stepping on the same rakes. With it, they step on new ones.

Pitfalls

The most interesting part. All 498 tests run on mocks. Mocks are how the agent imagines the API. Reality turned out to be more inventive.

1. Address = warehouse

The first assembled cart looked perfect. I opened the app—and half the items were marked “Sold out”: onions, chicken breast, carrots, cucumbers, and cottage cheese. Yet the API honestly replied “in stock” and cheerfully accepted them into the cart.

It turned out that Lavka has no “warehouse in general.” There is a dark store, and it is determined by the address. The agent took the first address from Favorites, while a different address was selected in my app. Different warehouses mean different prices and different stock levels.A similar trap was described by the author of yandex‑lavka‑mcp, but in his case products were mixed in from “Bolshaya Lavka,” whereas in my case, because of the address, the entire cart went to a different warehouse.

Осьминог, холодильник и 498 тестов: как я за 4 дня навайбкодил бота, который собирает мне корзину в Лавке / Хабр

Remedy:

  • the address and location identifier always change together: change the address and the old warehouse context is erased;

  • availability can only be checked on the product card: the `available` flag in search is catalog-level and cannot be trusted;

  • after adding — pause for 2 seconds and reread the cart: the item must be present and not marked “out of stock”;

  • check “is this the right warehouse” — the price of a control item (rice, buckwheat) must match the app exactly, down to the ruble.

Mocks would never have caught this: there is only one warehouse in the mock.

2. Browser cookies with a surprise

To avoid copying cookies by hand, the agent wrote an export utility directly from the Chromium profile: decryption via Windows DPAPI and AES‑GCM. It did not work. In newer versions of the Chromium database schema, 32 bytes containing the domain’s SHA‑256 hash are prepended to the value. Without stripping them, httpx crashed on non‑ASCII bytes in the header.

Bonus: the browser holds an exclusive lock on the database, so it must be closed completely, including background processes.

For the bot, I made it simpler and safer. A person opens a page from the bot, inserts the Cookie string from DevTools, and it is sent over HTTPS directly to the server, bypassing the chat history. The server uses Telegram’s signature to verify who sent it:

def verify_init_data(init_data: str, bot_token: str, *, max_age: int = 3600) -> int: """Verify the Telegram Web App signature and return the user’s Telegram ID."""pairs = dict(parse_qsl(init_data or "", keep_blank_values=True)) received = pairs.pop("hash", "") check = "\n".join(f"{k}={v}" for k, v in sorted(pairs.items())) secret = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest() expected = hmac.new(secret, check.encode(), hashlib.sha256).hexdigest() if not received or not hmac.compare_digest(expected, received): raise InitDataError("signature mismatch") ...

3. The agent was running yesterday's server

The agent added a 15th tool and ran the tests—they passed. I start the server—there are 14 tools. Twice.

The package was being installed into .venv without development mode: the builder put a copy of server.py into site-packages, and uv run rebuilt itnot always. It can only be fixed by explicitly reinstalling the package. Now this is recorded in the project rules, which the agent reads at the beginning of every session.

4. The model thinks while the menu cools down

By default, the menu model “reasons” before responding. A measurement showed that ~95% of the response tokens were spent on reasoning, and the weekly menu with recipes did not fit within 120 seconds. I turned reasoning off for “return JSON according to the schema” tasks, and the menu began arriving in a reasonable time.

5. The same lunch five days in a row

The “5 days, two people, 5000 ₽” run returned a menu of ~1000 kcal per person per day made from 6 ingredients. The lunch was the same all five days. Formally, the budget was met, with nothing to criticize.

I gave the model these priorities: calorie content → budget → variety → macronutrient balance.And most importantly — the code performs the first three self-checks: calories within ±15% of the target, lunch is not repeated without the “repeat” flag, lunch ≠ dinner on the same day, and no single protein source appears in more than half of the lunches and dinners. If a check fails, the menu goes back to the model for revision with a specific issue. The macronutrient balance remained a soft guideline: if you impose all the restrictions on the model at once, it satisfies none of them.

6. Three packages of onions

A draft for two people: three packages of onions and three packages of beef. The arithmetic was right. The model specified 950 g of onions in the menu, while the code rounded package counts strictly up: 900 g of beef was needed, and a package weighs 400 g, so three.

Two fixes. In the prompt — “30–40 g of onion and carrot per person per dish.” In the code — a tolerance rule: if the shortfall to a whole package is no more than 15% of its weight, we don’t take the extra package. 900 g out of 2 × 400 g is an 11% shortfall; we can live with that.

A related issue: Lavka has a limit on the quantity of a single item in the cart. The menu wanted 2.2 kg of beef shank, but Lavka had only one 750-g package. Now, a product like this is sent back for menu revision as “not enough” rather than disappearing silently.

7. “Cottage cheese” is not “5% cottage cheese”

There was “Cottage cheese” in the fridge, while the menu had “5% cottage cheese.” The bot did not recognize the cottage cheese and bought more. Matching by words instead of by exact matching solved the problem, and a test locked in the fix.

Why I don’t post the code

A reasonable question: “Can I do it myself?” The code is not publicly available. Reasons:

  1. Unofficial API. This is automation through a private web API using user cookies.How Yandex will react to this is not for me to decide — and neither is it for you to decide for me

  2. Cookies = the entire Yandex account. A ready-made bot, set up “following instructions from the internet,” stores the most valuable thing a user has from the very first day. Making this genuinely secure is difficult

  3. Responsibility. If someone else’s copy leaks, someone will lose access to their account or get an order from the wrong warehouse; I don’t want to be held responsible

If you want to build something like this for yourself, everything you need is in this article: the “the model proposes, the code verifies” principle, the spec → plan → TDD → review process, and a list of pitfalls. The agent will put the rest together in a couple of evenings — and you’ll understand every line, because you made every decision yourself.

What I took away from vibe coding

  1. The process matters more than the model. A spec, plan, tests, and review turn “the agent wrote something” into predictable work. Without them, 143 commits in 4 days would have turned into 143 commits of chaos.

  2. The model proposes, the code verifies. Everything that can be calculated is calculated by the code. Calories, budget, packages, availability. An LLM is for what cannot be calculated: coming up with dinner.

  3. Mocks are the agent’s imagination about the API. Not a single test would have caught the 9 defects from the first live run.A live run is needed as early as possible.

  4. Agent memory is a file. STATE.md with a “Pitfalls” section has saved me more time than any model.

  5. A review by another agent always pays off. A fresh perspective from another model finds what the author has already become accustomed to.

  6. Decisions remain with the human. “Don’t spend money,” “links only to Lavka,” and “deactivate manually, without automatic subscription cancellation” are my decisions. The agent executes and safeguards them with tests, but does not make them.

Next, I plan to add Lenta, Perekrestok, and Pyaterochka. After all, the era of personalized prices is returning, and I don’t want to pay more.