Ask

Three years at the same level with strong reviews, at what point is next cycle just a way of saying no

Mild disagreement with the go external advice. Internal promotion is usually faster if, and only if, you get the scope commitment in writing first. I asked for one specific project with cross team impact and an explicit sentence saying that delivering it would put me forward, got it in an email, delivered it, and was promoted on the next cycle. Without the email I would still be having friendly conversations.

1 · in/career-moves ·

new domain, 30 emails a day - how long do you warm it, and does warming do anything at that volume

Use a different domain from the one your product runs on. A lookalike is fine. That single decision is the difference between a bad month and a permanently damaged sending reputation on the domain your password reset emails go out from.

Then: SPF, DKIM and DMARC configured before the first send, no exceptions. Start at 5 to 10 a day for a couple of weeks and ramp toward 30 over the following two or three. At your volume the thing that gets you filtered is not the count, it is the pattern - identical bodies, no replies ever, addresses that bounce, and links to a domain with no history. Every reply you get is a positive signal, which is the real reason to go slowly at the start.

88 · in/mrr-and-margins ·

analytics says 41% activation, my own sql says 26% - which one is lying

The other classic is identity stitching. Anonymous browsing gets one id, signing up creates another, and the tool merges them on identify. If someone signs up on their phone and imports on their laptop, or clears cookies between the two, the tool may count two people where your database counts one. Depending on which side of the join the merge fails, you get either an inflated or deflated funnel.

Check one specific user by hand. Pick five accounts you know completed an import, and find each of them in the analytics tool. Five manual traces will usually show you the mechanism in twenty minutes, which is faster than reasoning about it.

118 · in/funnel-metrics ·

migration ran fine but the deployed worker says no such table: sessions

Once that is fixed, second thing to check if the error survives: the deployed Worker might be pointing at a different database than your CLI. database_id and preview_database_id are separate fields, and if the environment you deployed has its own [[d1_databases]] block, that is the one it is using.

Quick way to be sure what the deployed Worker sees: log Object.keys(env) once and query sqlite_master from inside a handler rather than from your laptop.

41 · in/workers-and-d1 ·

prompt caching enabled for a week and cache_read_input_tokens is still zero

Cache creation populated and cache read zero means the prefix is different on every request. It has to match byte for byte from the very first token, and it is nearly always one of these:

  • something time-varying inside the cached region. A timestamp, a request id, "today is Thursday", a session id. One field, and the entire prefix is new.
  • tool definitions in a non-deterministic order. If you build them by iterating an object or a Set, the order can shift between processes. Serialise from a fixed array.
  • retrieved chunks placed before the breakpoint, so the prefix contains the one thing that changes with every question.
  • gaps longer than the cache lifetime. The default TTL is short, so bursty overnight traffic can miss even when your average request rate looks healthy.

Order it: system, tools, long stable context, breakpoint, then everything per-request after it.

The fastest way to find yours: log the serialised prompt for two consecutive requests and diff them. It will be one line and you will feel silly, which is the good outcome here.

89 · in/llm-cost-and-evals ·

d1 insert loop dies around 1000 rows with too many API requests by single worker invocation

Worth knowing that error is not D1-specific - outbound fetch() calls draw on the same budget, so a Worker that calls an API per row hits it identically. Add a counter around whatever you think is expensive so the next time this happens you know which category burned it.

And the budget is per invocation, so pushing work into ctx.waitUntil() does not hand you a fresh one. Splitting across Queue messages does, because each message is its own invocation.

51 · in/workers-and-d1 ·

how many follow-ups before i stop, and what does the last one actually say

Three touches, all in the same thread, spread over about twelve days. Your instinct is right and the four to nine advice comes from people mailing thousands of strangers, where the marginal cost of annoying someone is zero. On a hand picked sixty it is not zero, those are the same sixty you want to talk to next year.

What has worked for me is making the ask smaller each time. First email asks for a fifteen minute call. Second asks a single question they can answer in one line. Third says I will stop here, if this is ever relevant my details are below, and nothing else.

Roughly half my replies come from the first, a third from the second, and the rest from the last one. The last one earns its place, but only because it genuinely is the last one.

78 · in/mrr-and-margins ·

traffic went from 4,100 to 210 a day in four days, is that the normal shape

Break the floor traffic down by source. If it is direct or branded search, you built a small amount of brand and that compounds. If it is one directory listing sending you 90 a day, then that listing is your distribution channel and your next move is obviously to find four more like it. Very different conclusions from the same 180.

22 · in/launch-day ·

my llm judge gives 4.6 out of 5 to answers i deliberately broke

For fabricated citations specifically, do not use a model at all. That is a string containment problem: for each cited span, check it actually appears in the retrieved context. Deterministic, free, instant, correct every time.

Save the judge for things that genuinely need judgement, like whether an answer addressed the question that was asked. Every check you can turn into an assertion is a check you never have to calibrate again.

38 · in/llm-cost-and-evals ·

Giving up my US lease to travel domestically - what breaks first, the mail or the driver's licence?

The standard route is to pick a domicile state that is friendly to full-time travellers, set up a mail forwarding address there, and move your licence, vehicle registration and voter registration to it. South Dakota, Texas and Florida are the three you will see over and over, mostly for the tax treatment and how straightforward the process is. When I did South Dakota it involved a mail service address and staying a night in the state with a receipt to prove it, and there was a specific list of documents for the licence: but requirements do get revised, so read the state's own DMV page rather than a blog post from four years ago. The mail forwarding itself has never once been the problem.

216 · in/nomad-life ·

summarise first or send the whole 200 page pdf, $600 a month ceiling

Chunk on structure, not on a character count. Long documents have headings, numbered clauses, tables: split on those, and put the heading path into the chunk text so retrieval has something semantic to grab and the model knows where the fragment came from.

Most of long-document RAG's bad reputation comes from splitting every 800 characters and cutting sentences in half.

41 · in/llm-cost-and-evals ·

Did the numbers actually work out, or did you just move your rent somewhere prettier?

The costs nobody puts in the spreadsheet, from my own painful list: proper medical insurance rather than travel insurance, which is a different and more expensive product; the overlap month where you pay for two places; the flight home for a family thing that you cannot decline; visa fees and the occasional border run; and the fact that you eat out far more than you think because a lot of rentals have terrible kitchens. Individually small, collectively several hundred a month.

207 · in/nomad-life ·