🏑


  1. September 27, 2026
    1. πŸ”— osolmaz/pi-workflows v0.17.5 release

      Wake recovery

      The workflow server no longer kills itself after laptop sleep. The authenticated owner re-arms its own expired lease when the claim row still carries its identity, so a server that slept keeps serving instead of restarting on every wake.

      Takeover now requires the recorded holder to be provably not serving: a starting server probes the holder's socket with a bounded hello check before removing the lock file, and the epoch claim row stays the fencing authority. A superseded holder's exit unlinks the socket path the replacement now serves, so the replacement re-creates the socket file within one poll tick, and a fenced takeover attempt restores the holder's lock record instead of leaving it lockless.

      Clients re-spawn a failed replacement within the start window (capped at three attempts) instead of warning after the full timeout, so one lost race costs milliseconds.

      Fixes

      • Lease self-renewal across suspend: expiry gates takeover, it never kills the owner.
      • Serving-probe lock takeover with epoch-claim fencing; concurrent starters converge instead of racing.
      • The claim loser's cleanup can no longer unlink the winner's bound socket, and a superseded holder's exit cannot strand the replacement.
      • A fenced takeover attempt restores the displaced holder's lock record.
      • Client re-spawn on dead replacements, bounded by the start window.
      • Documented the wake recovery behavior in docs/WORKFLOWS.md.
    2. πŸ”— Confessions of a Code Addict How Copy-on-Write Works with Memory-Mapped Files rss

      In the last video, we discussed demand paging. Now, let's move towards an even more interesting topic: copy-on-write (CoW). Just like demand paging, CoW is the kernel's internal mechanism with implications for the performance of user- space systems. It enables multiple processes to share data in RAM between them in read-only mode. The interesting bit is that the processes themselves are not aware of this sharing, as far as they are concerned they are executing as if they are the only ones working with that data. Of course, this works as long as the processes are reading the data. When one of them needs to do a write to this shared data, the kernel needs to make a copy before the write can happen, hence the name "copy-on-write "!

      This is a very wide and deep topic, so I'm going to split into multiple videos. This first video goes deep inside the kernel to explain what CoW is, how the kernel implements it, and for that we will take the example of mmap to read and write files.

      Following are some of the major sections in the video with the timestamps to help you navigate. Also, I recommend watching the video at higher speed to get a better experience.

      • 00:00 -- Why CoW matters: memory use, page faults, and unpredictable latency in data-intensive applications.

      • (06:34) The page-table picture: how different processes can map the same physical frame.

      • (11:44) CoW in one diagram: share a page while reading; make a private copy when writing.

      • (15:06) Mapping a file withmmap: the call's arguments, including MAP_PRIVATE.

      • (21:31) Whatmmapcreates: a virtual address range and VMA, before the file page is maps into the process.

      • (25:03) The first read: address translation, a page fault, and how the kernel resolves it.

      • (32:25) The page cache: where file data is held in RAM and why another process can reuse it.

      • (38:43) A second process maps the file: its own page fault leads to the same cached physical page.

      • (43:11) A private write: why writing to that shared file page would violate MAP_PRIVATE.

      • (45:51) Write protection and CoW: how a read-only PTE causes a write fault and the kernel gives the writer a private copy.

      • (49:07) What happens next: the second process can also get a copy; later writes to an already private page proceed without another CoW fault.

      A minor correction note : At about 46 minutes, when I say mappings of page-cache pages are read-only, I mean the MAP_PRIVATE mappings in this example. A writable MAP_SHARED mapping can modify a cached file page, which is later written back to the file.

      If you are new to this series, it is based on my ebook called "Virtual Memory from First Principles". It is available to read for free online and also available to purchase from Gumroad (PDF/Epub) and Amazon (Kindle edition).

      Buy PDF/Epub

      Get Kindle Edition


      And, if you want to watch the previous videos in this series, the following is what has been published so far:

      Share

      Read more

    3. πŸ”— Julia Evans Replacing the old battery on rechargeable bike lights rss

      Hello! Recently I needed bike lights for my bike. And I remembered that I already had rechargeable bike lights that I bought ten years ago, that I hadn't tried in a long time. I tried to recharge them, but after fully charging them, they only worked for maybe 5 minutes before they turned off again.

      I don't know much about electronics, but I've been curious about whether it's possible to fix old electronics for a long time, and this seemed like the perfect repair project because I might just need to replace the battery.

      So I went to the local queer makerspace where I'm a member to use the soldering iron and try to do it! I don't know much about electronics and this post does not contain any safety advice because I don't know much about safety. I think it's nice to do projects in a community space where you can get help.

      step 1: cut it open

      The bike light felt like it was made of silicone, so I cut open the silicone in a haphazard way along something that vaguely looked like a seam.

      I definitely ripped some silicone in the process and it was pretty messy but I got it open and found the circuit board.

      I don't know the model number of the bike lights but there's a photo of them at the end of the post.

      step 2: remove the screws

      There were some screws attaching things together so I removed them so I could get the circuit board out.

      Mostly I tried to remove as few screws as possible because I was worried about losing them or not being able to put them back after. I probably put the screws in a bag or something.

      step 3: get the circuit board out

      I took out the circuit board. Here's what it looked like:

      You can see where the battery is attached, I think it's left of RI3 and above Q2.

      Here's what the battery looked like:

      step 4: desolder the battery

      I'd never desoldered anything before, so I found the iFixit guide to desoldering and read it. Also I asked my friends Lee and Lauria for advice.

      Here were the steps I ended up following based on the guide & the advice I got:

      1. Use a desoldering pump to remove most of the solder
      2. Once most of it is gone, kind of pull them apart to try to separate them
      3. Also try to avoid getting the battery too hot in the process by taking breaks to let it cool down. I'm not very good with a soldering iron so it took a while.
      4. The battery has an attachment that is welded to the top. For a while I thought I needed to remove this and it seemed impossible, but it turned out the replacement battery comes with that part so actually I was supposed to leave it alone.

      step 5: identify the battery

      In the picture of the battery in Step 3, you can see it says something like "3" and "LI???77". There's a piece of metal that I think is welded or something to the top of the battery. It seemed impossible and also maybe not smart to try to remove so I wasn't sure how to find out what an "LI????77" was or how to order another one.

      I've been trying to avoid using LLMs (though I will not get into that because I am exhausted by LLM discourse and I'm sure you are too), but I really had no idea how to figure out what the battery was so I asked an LLM. It gave the response "LIR2477", which (when I looked it up) looked exactly the same as my battery so I figured that was plausible.

      I would be interested to learn non-LLM ways to figure this out though. There must be a way. Lauria showed me how to use DigiKey's search which was very cool though DigiKey didn't have that part.

      step 6: buy the battery

      I went to AliExpress and ordered:

      1. 2 batteries (I had 2 bike lights and I wanted to fix them both)
      2. some silicone glue to glue things back together

      I think the batteries were $3 each and the glue was $8.

      step 7: solder the new batteries in and glue it back together

      The parts took maybe 2 weeks to arive, and once they arrived, I went back to the makerspace and:

      • soldered in the new batteries
      • put the screws back in. The screws were very small and hard to hold, so at this point I dropped some screws on the ground and couldn't find them because they were too small. So I just used fewer screws and hoped for the best.
      • used the glue to try to put everything back together.
      • Make a somewhat halfhearted attempt to clamp the parts I was gluing together

      Then after waiting some amount of time for the glue to dry I took it home and waited 24 hours for the glue to cure.

      Also I took the old batteries to somewhere nearby that accepts old batteries.

      it works!

      The lights work! I have used them to bike at night! I still haven't needed to recharge them (and tragically I had to order a new Mini USB cable because I got rid of all my Mini USB cables, so I'm still waiting for that), so I still don't know for sure how long the lifetime of the new battery will be.

      Here's what the light looks like after re-gluing. You can see that I didn't glue very carefully. It didn't really go back together that well but I'm hoping it'll be good enough.

      I thought it was really cool that I was able to do this with extremely minimal electronics skills! It cost about $20 CAD to buy the parts, and (whether or not the repair holds up, I'll try to update this post in the future!), it was fun to try to repair something and learn something new.

  2. September 26, 2026
    1. πŸ”— backnotprop/plannotator v0.27.21 release

      Follow @plannotator on X for updates

      Missed recent releases? Release | Highlights
      ---|---
      v0.27.20 | Mistral Vibe support, annotate gets the full Options menu and Settings, jj Commits panel, long lines wrap in plan code blocks
      v0.27.19 | Before/After image previews in code review, file comments as GitHub file threads, forge-correct #123 links, /plannotator-last finds the right session
      v0.27.18 | Model pickers from your installed Claude and Codex (Opus 5.5, Fable 5.1, GPT-6), unsent PR review comments survive new pushes
      v0.27.17 | Diagram files open in the diagram viewer, OpenCode switches model with agent, idle review stops polling the git remote, Tree is the default review view
      v0.27.16 | Themed diagrams on Mermaid 12, comment on any node or edge, patch-file review, embedded HTML documents render
      v0.27.15 | Plannotator TUI and Herdr Annotate announcement, element context on pinpoints, HTML links open as linked documents, All files panel, Classic diff default
      v0.27.14 | Pi plan progress survives compaction, Codex threads across rollout files, WSL browser setting, Mod+E edit mode
      v0.27.13 | Open a review on a specific base (--base, --diff-type), symlink containment on /api/doc, CI flake fix, Amp decision relay
      v0.27.12 | Unified decision control, token hover cards, local-vs-remote diff, approval notes
      v0.27.11 | OpenCode server leak fix, durable local feedback archive, unknown-subcommand fix
      v0.27.10 | Auto-viewed files on scroll, annotation undo/redo, OpenCode 2 slash commands restored, npm 12 agent terminal fix
      v0.27.9 | WebMCP browser-agent tools, HTML refresh from disk, host seams, lazy renderers, Windows uninstall fix

      What's New in v0.27.21

      A fix release built mostly from community reports. Remote and phone sessions load several times faster, code review can request changes on GitHub for real, model pickers show readable names and say where their list came from, and several OpenCode rough edges are gone. Seven pull requests, answering issues from four people.

      Remote and phone sessions load several times faster

      Every session sent the whole app to the browser as one uncompressed file, about 25 MB for plan review and 18 MB for code review, on a fresh port each time, so nothing could be cached. On a fast local connection that goes unnoticed. Over a slow tunnel, such as a phone reaching a Mac through Tailscale, it meant waiting up to two minutes before anything appeared.

      Remote-mode and --tailscale sessions now send the page compressed: brotli over --tailscale's HTTPS, gzip over plain http. Measured cold loads at 206 KB/s and 159 ms, the connection from the report:

      | Before | --tailscale | Remote mode
      ---|---|---|---
      Plan review | 118 s | 30 s | 34 s
      Code review | 86 s | 21 s | 24 s

      Local sessions send exactly the same bytes and headers as before, since compressing on the same machine gains nothing. The page is also about 1 MB smaller for everyone: KaTeX's math fonts no longer ship in two older formats that no supported browser loads. Splitting the app into separately loaded pieces, which measured around 5 to 7 seconds on the same link, is the next step and is tracked in the same issue.

      (#1619, refs #1617, reported by @giladbarnea)

      Request changes on GitHub pull requests

      In a pull request review, Post comments, then… promised a choice between requesting changes and staying neutral, and Request changes… promised the same. Neither existed: every review posted as a plain comment. The submission dialog now offers Comment or Request changes, and a Request changes review posts as REQUEST_CHANGES on GitHub, including reviews with file-level comment threads. On your own pull request the option is disabled with the reason, since GitHub refuses it. GitLab has no equivalent, so the option is disabled there and posting is unchanged.

      (#1613, closing #1611, reported by @RobertoArtiles)

      Model pickers show real names and where the list came from

      Claude Code 2.1.282 changed how it describes its models, and Plannotator started labeling specific Claude versions with their marketing tagline instead of their name: several rows read "Best for everyday, complex tasks" and could not be told apart. Every Claude picker now shows the model's name again, and older Claude Code versions keep working.

      Each Claude and Codex model picker (code review agents, Code Tour, Guided Review, Ask AI) also shows where its list came from, such as "From your installed Codex 0.155.1", or says it is using the built-in list and suggests updating or signing in to the tool. The built-in list, used only when Plannotator cannot read your installed tool's models, now includes the GPT-6 models. Guided Review defaults to GPT-6 Luna for Codex when your Codex offers it, since guides generate faster on a lighter model; a model you already picked always wins.

      If a newer model is missing from your picker, update the tool itself (codex update, or update Claude Code) and restart the review. The picker shows whatever your installed tool reports.

      (#1615, #1616)

      OpenCode fixes

      • Feedback reaches the right agent. /plannotator-last and /plannotator-annotate feedback was answered by OpenCode's default agent even when the annotated message came from a different one. Feedback now goes to the agent that wrote the message, as long as OpenCode still lists it; otherwise it is delivered exactly as before. (#1614, closing #1612, reported by @balaji-dutt)
      • Guided Review and review agents work with OpenCode v2. Plannotator started opencode run with a --dir flag that OpenCode v2 rejects. It now starts OpenCode in the review's folder and sets its working-directory environment to match, which works on both versions and also keeps OpenCode 1.x from reviewing the wrong checkout in PR, worktree and multi-repo reviews. (#1610, refs #1609, reported by @JakobHavtorn)

      Additional Changes

      • Selections elsewhere on the page survive. The document viewer cleared the whole page's text selection whenever it loaded or its content changed. It now only clears a selection inside itself. This mainly affected apps that embed the viewer next to other panels. (#1608)

      Install / Update

      macOS / Linux:

      curl -fsSL https://plannotator.ai/install.sh | bash
      

      Windows:

      irm https://plannotator.ai/install.ps1 | iex
      

      Claude Code Plugin: Run /plugin in Claude Code, find plannotator , and click "Update now".

      Pi: Update @plannotator/pi-extension to 0.27.21 and restart Pi.

      OpenCode: Clear cache and restart:

      rm -rf ~/.bun/install/cache/@plannotator
      

      What's Changed

      • fix(ui): vim selection reset only clears selections inside the viewer by @backnotprop in #1608
      • fix(agents): drop --dir from opencode run (OpenCode v2 rejects it) by @backnotprop in #1610
      • fix(review): make Request changes a real PR review event by @backnotprop in #1613
      • fix(opencode): answer /plannotator-last feedback with the annotated message's agent by @backnotprop in #1614
      • fix(core): name Claude models from displayName when the description is only a tagline by @backnotprop in #1615
      • Model pickers: show where the list came from; refresh the Codex fallback by @backnotprop in #1616
      • perf: compress the app page for remote and tailnet sessions; woff2-only KaTeX by @backnotprop in #1619

      Community

      This release is mostly answers to reports:

      • @giladbarnea measured the two-minute phone load precisely, down to the link speed and what compression alone would save, in #1617
      • @RobertoArtiles traced the missing request-changes choice through the source in #1611
      • @balaji-dutt reported OpenCode feedback reaching the wrong agent, with an agent-by-turn table, in #1612
      • @JakobHavtorn reported the OpenCode v2 --dir failure in #1609

      Full Changelog : v0.27.20...v0.27.21

    2. πŸ”— @HexRaysSA@infosec.exchange AI agents can now work in IDA, using the official IDA MCP server. πŸ”§ mastodon

      AI agents can now work in IDA, using the official IDA MCP server. πŸ”§

      IDA MCP is free and open source. It's model-agnostic, so you can use any capable LLM, running locally or in the cloud.

      What's different:
      β†’ Code Mode: a small set of tools, with agents working through IDAPython. That uses ~20% fewer tokens than popular alternatives
      β†’ IDA Nexus: many agents and humans on the same IDBs, with changes showing instantly in the IDA GUI (and vice versa)
      β†’ Runs headless with idalib for large-scale pipelines, and fully local for air-gapped environments
      β†’ Works with IDA Pro, Home, Classroom and OEM

      One-command installs for Claude Code, Codex, GitHub Copilot, Pi and oh-my-pi. Any stdio MCP client works too.

      Install (requires uv):
      uvx ida-hcli mcp install

      Full details πŸ‘‡
      https://hex-rays.com/blog/hex-rays-ida-mcp-server

    3. πŸ”— r/Harrogate Best Lunch Deals rss

      As the title says, what are the best restaurant lunch deals in Harrogate mid- week? Seen Pranzo 2 courses for Β£20 and Tannin Level 2 course for Β£23… appreciate any other suggestions!

      submitted by /u/Various-Note-9396
      [link] [comments]

    4. πŸ”— r/Harrogate Coldbath Road Reccomendations rss

      Any insight?? The old man is coming up tomorrow at 11am what is there to do on coldbath?

      I’ve only ever walked up is there anywhere for a coffee or lunch you would advise on

      submitted by /u/FrontRaspberry5060
      [link] [comments]

    5. πŸ”— Anton Zhiyanov Go concurrency distilled rss

      This mini-book provides a brief overview of many concurrency topics in Go. Each topic comes with interactive examples β€” feel free to experiment with them by changing the code and clicking Run. There's also a PDF version with static examples.

      This is a quick refresher on Go concurrency, not a beginner's guide. If you want to learn concurrency from the ground up with practical exercises, check out my other book β€” Gist of Go: Concurrency.

      The book is AI-free.

      Goroutines β€’ Channels β€’ Select β€’ Pipelines β€’ Time β€’ Context β€’ Wait groups β€’ Data races β€’ Race conditions β€’ Mutexes β€’ Semaphores β€’ Signaling β€’ Run once β€’ Object pool β€’ Atomics β€’ Testing β€’ Scheduling β€’ Diagnostics β€’ Final thoughts

      # Goroutines The foundation of concurrency in Go is goroutines – functions started with the go keyword: func main() { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() fmt.Println("worker 1") }() go func() { defer wg.Done() fmt.Println("worker 2") }() wg.Wait() } worker 2 worker 1 The Go runtime juggles these goroutines and distributes them among operating system threads running on CPU cores. Compared to OS threads, goroutines are lightweight, so you can create hundreds or thousands of them. Goroutines are completely independent. The main function is also a goroutine, but it starts implicitly when the program starts. When main ends, other goroutines also shut down. We use a wait group (sync.WaitGroup) to wait for goroutines to finish in the example above. A wait group has a counter inside. Calling Add(n) increments it by n, while Done() decrements it by one. Wait() blocks the calling goroutine (in this case, main) until the counter reaches zero. This way, main waits for both workers to finish before it exits. WaitGroup.Go automatically increments the wait group counter, runs a function in a goroutine, and decrements the counter when it's done: func main() { var wg sync.WaitGroup wg.Go(func() { fmt.Println("worker 1") }) wg.Go(func() { fmt.Println("worker 2") }) wg.Wait() } worker 2 worker 1 # Channels Goroutines can pass values to each other through channels. A channel is like a window where one goroutine can throw something and another can catch it: func main() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg) } ping Sending a value through a channel is a synchronous operation. When the sending goroutine writes a value to the channel (ch <- val), it blocks and waits for someone to receive that value (<-ch). Only then does it continue. Output channel Returning an output channel from a function and filling it within an internal goroutine is a common pattern in Go. This allows the caller to receive values through the channel while the owning function retains control of it: func generate(start, stop int) chan int { out := make(chan int) go func() { for i := start; i < stop; i++ { out <- i } }() return out } Closing a channel To signal readers that all data has been sent, the writer goroutine closes the channel with close(): func generate(start, stop int) chan int { out := make(chan int) go func() { defer close(out) for i := start; i < stop; i++ { out <- i } }() return out } The reader checks the channel's status with a second value ("comma OK") when reading: func main() { in := generate(5, 10) for { num, ok := <-in if !ok { break } fmt.Print(num, " ") } } 5 6 7 8 9 While the channel is open, the reader receives the next value and a true status. If the channel is closed, the reader gets a zero value and a false status. A channel can only be closed once. Closing it again or writing to a closed channel causes a panic. The only reason to close a channel is to signal to its readers that all data has been sent. If this isn't important to the readers, then you don't need to close it. When a channel is no longer used, Go's garbage collector will free its resources, whether it's closed or not. Channel iteration range automatically reads the next value from the channel and checks if it's closed. If the channel is closed, it exits the loop: func main() { nums := generate(5, 10) for n := range nums { fmt.Print(n, " ") } } 5 6 7 8 9 Range over a channel returns a single value, not a pair, unlike range over a slice. Directional channels You can protect yourself from accidental write/close errors by setting the channel direction. Channels can be: chan (bidirectional): for reading and writing (default); chan<- (send-only): for writing only; <-chan (receive-only): for reading only. You can't read from a send-only channel or write to a receive-only channel (nor can you close it). Channels are usually initialized for both reading and writing, and specified as directional in function parameters. Go automatically converts a regular channel to a directional one: stream := make(chan int) go func(in chan<- int) { in <- 42 }(stream) func(out <-chan int) { fmt.Println(<-out) }(stream) 42 Buffered channels Buffered channels work like a FIFO queue with a fixed-size buffer for storing values. As long as the buffer has free space, writing to the channel doesn't block the goroutine. Similarly, as long as the buffer contains values, reading from the channel doesn't block the goroutine: stream := make(chan int, 3) stream <- 11 stream <- 12 stream <- 13 fmt.Println(<-stream) fmt.Println(<-stream) 11 13 By default, if you don't specify a buffer size, a channel is unbuffered (buffer size equals zero). Buffered channels work with the built-in len() and cap() functions: stream := make(chan int, 3) stream <- 11 fmt.Println(cap(stream), len(stream)) 3 1 Reading from a closed buffered channel returns values from the buffer and a true status. Once all values are taken, it returns a zero value and a false status, like a regular channel: stream := make(chan int, 1) stream <- 11 close(stream) val, ok := <-stream fmt.Println(val, ok) // 11 true val, ok = <-stream fmt.Println(val, ok) // 0 false 11 true 0 false nil channel Like any type in Go, channels have a zero value, which is nil. Writing to or reading from a nil channel blocks the goroutine indefinitely: var stream chan int go func() { // blocks forever stream <- 1 }() // blocks forever <-stream Closing a nil channel causes a panic: var stream chan int close(stream) // panic: close of nil channel # Select The select statement is somewhat like switch, but specifically designed for channels. Here's what it does: Checks which cases are not blocked. If multiple cases are ready, randomly selects one to execute. If all cases are blocked and there is a default case, executes it. If all cases are blocked and there is no default case, waits until one is ready. Select is used to manage data flow in pipelines: // merge sends values from in1 and in2 to the output channel. func merge(in1, in2 <-chan int) <-chan int { out := make(chan int) go func() { defer close(out) for in1 != nil || in2 != nil { select { case val1, ok := <-in1: if ok { out <- val1 } else { in1 = nil } case val2, ok := <-in2: if ok { out <- val2 } else { in2 = nil } } } }() return out } // Suppose we send 10..12 to in1, 20..22 to in2, // and call merge(in1, in2) 10 11 20 12 21 22 To cancel goroutines: // process modifies values from in and send them to out // until in is exhausted or cancel is closed. func process(cancel chan struct{}, in <-chan int) <-chan int { out := make(chan int) go func() { for val := range in { select { case out <- val*10: case <-cancel: fmt.Println("canceled") return } } }() return out } // Suppose we send values 11 and 12 to in // and then call close(cancel) 110 120 canceled For non-blocking operations: // multiplier returns a function that multiplies // the input by 10 and sends it to the channel // or returns an error if the channel is busy. func multiplier(ch chan<- int) func(n int) error { return func(n int) error { select { case ch <- n*10: return nil default: return errors.New("busy") } } } func main() { nums := make(chan int, 1) multiply := multiplier(nums) err := multiply(11) fmt.Println(<-nums, err) // 110 <nil> err = multiply(12) fmt.Println(<-nums, err) // 120 <nil> err = multiply(13) err = multiply(14) fmt.Println(err) // busy } 110 <nil> 120 <nil> busy And for much more. # Pipelines A pipeline is a sequence of operations where each step takes input data, processes it in a specific way, and outputs it. The input and output of each operation is a channel. A typical pipeline looks like this: Reader : Reads input data from a file, database, or network. N processors : Transform, filter, aggregate, or enrich data using external sources. Writer : Writes the processed data to a file, database, or network. func readT any <-chan T { out := make(chan T) go func() { defer close(out) for { // read data from somewere data := // ... out <- data } }() return out } func processT any <-chan T { out := make(chan T) go func() { defer close(out) for inData := range in { // process the data outData = // ... out <- outData } }() return out } func writeT any <-chan struct{} { done := make(chan struct{}) go func() { defer close(done) for data := range in { // write the data } }() return done } Output channel A goroutine can signal other goroutines that it has finished its work using an output channel : func generate(start, stop int) <-chan int { out := make(chan int) go func() { defer close(out) for i := start; i < stop; i++ { out <- i } }() return out } func main() { nums := generate(5, 10) for n := range nums { fmt.Print(n, " ") } } 5 6 7 8 9 Done channel If a goroutine doesn't need to return results, it can signal completion using a done channel : func work() <-chan struct{} { done := make(chan struct{}) go func() { defer close(done) fmt.Println("work done") }() return done } func main() { done := work() <-done } work done Cancel channel To terminate a goroutine early, a calling goroutine can use a cancel channel : func generate(cancel chan struct{}, n int) <-chan int { out := make(chan int) go func() { defer close(out) for i := 1; i <= n; i++ { select { case out <- i: case <-cancel: return } } }() return out } func main() { cancel := make(chan struct{}) defer close(cancel) nums := generate(cancel, 10) fmt.Println(<-nums) fmt.Println(<-nums) fmt.Println(<-nums) } 1 2 3 Error handling There are three approaches to error handling in concurrent pipelines. ➊ Return on the first error: // calculate produces answers for the given numbers. func process(in <-chan int) (<-chan int, <-chan error) { out := make(chan Answer) errc := make(chan error, 1) go func() { defer close(out) for n := range in { ans, err := fetchAnswer(n) if err != nil { errc <- err // return with error return } out <- ans } errc <- nil // return with nil }() return out, errc } βž‹ Use a result type: // Result contains an answer or an error. type Result struct { answer int err error } // calculate produces answers for the given numbers. func calculate(in <-chan int) <-chan Result { out := make(chan Result) go func() { defer close(out) for n := range in { ans, err := fetchAnswer(n) out <- Result{ans, err} // return answer + error } }() return out } ➌ Collect errors separately: // calculate produces answers for the given numbers. func calculate(in <-chan int, errc chan<- error) <-chan int { out := make(chan Answer) go func() { defer close(out) for n := range in { ans, err := fetchAnswer(n) if err == nil { out <- ans // send answer } else { errc <- err // or error } } }() return out } # Time Besides handling date and time, the time package offers tools for managing time-sensitive operations in concurrent programs. After time.After() returns a channel that is initially empty, but receives a value after the timeout period. It's useful for timing out operations: // withTimeout executes a function with a given timeout. func withTimeout(timeout time.Duration, fn func()) error { done := make(chan struct{}) go func() { defer close(done) fn() }() // blocks until fn completes or the timer expires, // whichever happens first select { case <-done: return nil case <-time.After(timeout): return errors.New("timeout") } } withTimeout() waits for fn() to complete, but thanks to time.After(), it won't wait longer than the timeout duration: func main() { var err error // completes in time err = withTimeout( 50*time.Millisecond, func() { fmt.Println("work done") }, ) fmt.Println("err =", err) // gets canceled on timeout err = withTimeout( 50*time.Millisecond, func() { time.Sleep(100 * time.Millisecond) fmt.Println("work done") }, ) fmt.Println("err =", err) } work done err = <nil> err = timeout Timer A timer (time.Timer) is a structure with a C channel to which it sends the current time when it triggers (expires). Timers are useful for planning future executions: done := make(chan struct{}) timer := time.NewTimer(50 * time.Millisecond) go func() { eventTime := <-timer.C // blocks for 50ms fmt.Println("work done at", eventTime) close(done) }() <-done work done at 2009-11-10 23:00:00.05 Stop() stops the timer and returns true if it hasn't expired yet, and false otherwise: // timer expires after 50ms timer := time.NewTimer(50 * time.Millisecond) go func() { eventTime := <-timer.C fmt.Println("work done at", eventTime) }() // after 10ms, the timer hasn't expired yet time.Sleep(10 * time.Millisecond) if timer.Stop() { fmt.Println("execution canceled") } else { fmt.Println("too late to cancel") } execution canceled It's often more convenient to use the time.AfterFunc() wrapper function. It waits for duration d and then executes function f: done := make(chan struct{}) work := func() { fmt.Println("work done") close(done) } // executes work after 50ms time.AfterFunc(50*time.Millisecond, work) <-done work done time.AfterFunc() returns a timer that you can cancel before execution starts: // executes the function after 50ms timer := time.AfterFunc(50*time.Millisecond, func() {}) // after 10ms, the timer hasn't expired yet time.Sleep(10 * time.Millisecond) if timer.Stop() { fmt.Println("execution canceled") } execution canceled If a timer is used in a loop, it's better to create a single timer and reset it instead of creating a new instance on each iteration: // consumer reads tokens from the input channel and alerts // if a value does not appear in a channel after an hour. func consumer(in <-chan token) { const timeout = time.Hour timer := time.NewTimer(timeout) for { timer.Reset(timeout) select { case <-in: // do stuff case <-timer.C: // log warning } } } // Suppose we send 10,000 values to the in channel // and measure memory usage. Memory used: 4 KB, # allocations: 6 Ticker A ticker is like a timer, but it keeps firing until you stop it. Tickers are useful for executing periodic tasks: // fires every 50ms ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() go func() { for { // waits for ticker to fire on each iteration at := <-ticker.C fmt.Println("work done at", at) } }() // enough time for the ticker to fire 3 times time.Sleep(160*time.Millisecond) ticker.Stop() work done at 2009-11-10 23:00:00.05 work done at 2009-11-10 23:00:00.10 work done at 2009-11-10 23:00:00.15 NewTicker(d) creates a ticker that sends the current time to the channel C at interval d. You must stop the ticker eventually with Stop() to free up resources. If the channel reader can't keep up with the ticker, the ticker will skip ticks. # Context The main purpose of context is to cancel operations, either manually or by timeout/deadline. The function accepts a context and uses its Done() channel to listen for cancellation: // work performs a task for 50 ms unless canceled. // Returns an error when canceled. func work(ctx context.Context) error { done := make(chan struct{}) go func() { time.Sleep(50 * time.Millisecond) fmt.Println("work done") close(done) }() select { case <-done: return nil case <-ctx.Done(): return ctx.Err() } } Cancel manually (context.Canceled error): func main() { // empty context ctx := context.Background() // manual canellation context ctx, cancel := context.WithCancel(ctx) defer cancel() done := make(chan struct{}) go func() { // takes 50 ms unless canceled err := work(ctx) fmt.Println("err =", err) close(done) }() // cancels after 10 ms time.Sleep(10 * time.Millisecond) cancel() <-done } err = context canceled Cancel by timeout (context.DeadlineExceeded error): func main() { ctx := context.Background() // cancels after 10 ms ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond) defer cancel() done := make(chan struct{}) go func() { // takes 50 ms unless canceled err := work(ctx) fmt.Println("err =", err) close(done) }() <-done } err = context deadline exceeded Cancel by deadline (context.DeadlineExceeded error): func main() { ctx := context.Background() // cancels at now + 10 ms deadline := time.Now().Add(10 * time.Millisecond) ctx, cancel := context.WithDeadline(ctx, deadline) defer cancel() done := make(chan struct{}) go func() { // takes 50 ms unless canceled err := work(ctx) fmt.Println("err =", err) close(done) }() <-done } err = context deadline exceeded Context is layered. A context object is immutable. To add new properties to a context, a new (child) context is created based on the old (parent) context. The shorter timeout between the parent and child contexts always wins. The child context can only shorten the parent's timeout, not extend it: func main() { // parent context with a 100 ms timeout const dur100ms = 100 * time.Millisecond parentCtx, cancel := context.WithTimeout(context.Background(), dur100ms) defer cancel() // child context with a 10 ms timeout const dur10ms = 10 * time.Millisecond childCtx, cancel := context.WithTimeout(parentCtx, dur10ms) defer cancel() // now the work gets canceled err := work(childCtx) fmt.Println("err =", err) } err = context deadline exceeded Multiple cancels are safe. You can call cancel() on the context as many times as you want. The first cancel will work, and the rest will be ignored. You can specify a custom cancellation cause using context.WithCancelCause(), context.WithTimeoutCause() and context.WithDeadlineCause(). This cause is accessible through context.Cause(): ctx, cancel := context.WithCancelCause(context.Background()) cancel(errors.New("the night is dark")) fmt.Println(context.Cause(ctx)) the night is dark You can register a function to execute when the context is canceled with context.AfterFunc(): ctx, cancel := context.WithCancel(context.Background()) cleanup := func() { fmt.Println("cleanup") } context.AfterFunc(ctx, cleanup) cancel() time.Sleep(10 * time.Millisecond) cleanup Context can pass additional information about a call using context.WithValue(), which creates a context with a value for a specific key. But it's generally better to avoid passing values in context. It's better to use explicit parameters or custom structs instead. # Wait groups The sync.WaitGroup type lets you wait for one or more goroutines to finish: const n = 10 var wg sync.WaitGroup wg.Add(n) for range n { go func() { defer wg.Done() fmt.Print(".") }() } wg.Wait() .......... A WaitGroup doesn't know anything about the goroutines it manages. It works with an internal counter. Calling wg.Add(1) increments the counter by one, while wg.Done() decrements it. wg.Wait() blocks the calling goroutine until the counter reaches zero. The Go method combines Add, starting a goroutine, and Done: var wg sync.WaitGroup for range 10 { wg.Go(func() { fmt.Print(".") }) } wg.Wait() .......... All methods are safe to use from multiple goroutines. Normally, all Add calls happen before Wait. But technically, there's nothing stopping you from doing some of the Add calls before Wait and some after (from another goroutine). You can call Wait from multiple goroutines. They will all block until the group's counter reaches zero. # Data races A data race happens when multiple goroutines access shared data, and at least one of them modifies it. We need to protect the data from this kind of concurrent access. A data race doesn't always cause a runtime panic. That's why Go provides a special tool called the race detector. You can turn it on with the race flag, which works with the test, run, build, and install commands. var total int // There's a data race on total. var wg sync.WaitGroup wg.Go(func() { total++ }) wg.Go(func() { total++ }) wg.Wait() fmt.Println("total:", total) total: 2 go run -race main.go ================== WARNING: DATA RACE ... 2 Found 1 data race(s) Channels are safe for concurrent reading and writing, and they don't cause data races. Ways to prevent data races: Avoid concurrent data modification (typically by using channels). Synchronize access with mutexes. Use only atomic operations. Race conditions A race condition happens when an unpredictable order of operations from multiple goroutines leads to an incorrect system state: // There's a race condition when working with balance. withdraw := func(amount int) { if getBalance() < amount { return } time.Sleep(time.Millisecond) setBalance(getBalance() - amount) } setBalance(50) var wg sync.WaitGroup wg.Go(func() { withdraw(40) }) wg.Go(func() { withdraw(40) }) wg.Wait() fmt.Println("balance:", getBalance()) balance: -30 If individual operations are concurrent-safe, Go's race detector won't find any issues. Because of this, it doesn't catch race conditions: go run -race main.go balance: -30 You can't fully eliminate uncertainty in a concurrent environment. Events will happen in an unpredictable order β€” that's just how concurrency works. However, you can prevent a race condition β€” often by protecting a composite operation with a mutex: var mu sync.Mutex withdraw := func(amount int) { mu.Lock() defer mu.Unlock() if getBalance() < amount { return } time.Sleep(time.Millisecond) setBalance(getBalance() - amount) } setBalance(50) var wg sync.WaitGroup wg.Go(func() { withdraw(40) }) wg.Go(func() { withdraw(40) }) wg.Wait() fmt.Println("balance:", getBalance()) balance: 10 Compare-and-set Sometimes you can prevent a race condition without using mutexes by applying an atomic compare-and-set operation or one of its flavors: // CompareAndSet changes the value to new if the current value equals old. // Returns true if the value was changed. CompareAndSet(old, new any) bool // CompareAndSwap changes the value to new if the current value equals old. // Returns the old value. CompareAndSwap(old, new any) any // CompareAndDelete deletes the value if the current value equals old. // Returns true if the value was deleted. CompareAndDelete(old any) bool // etc The idea is always the same: Check if the assumed (old) state matches reality. If it does, change the state to new. If not, do nothing. # Mutexes The sync.Mutex type protects shared data and parts of your code from being accessed concurrently: var total int var mu sync.Mutex var wg sync.WaitGroup for range 100 { wg.Go(func() { mu.Lock() time.Sleep(time.Millisecond) total++ mu.Unlock() }) } wg.Wait() total: 100 The mutex guarantees that only one goroutine can run the code between Lock() and Unlock() at a time. A mutex is used in these situations: When multiple goroutines are modifying the same data. When one goroutine is modifying the data and others are reading it. If all goroutines are only reading the data, you don't need a mutex. TryLock The TryLock method tries to lock the mutex, just like a regular Lock. But if it can't, it returns false right away instead of blocking the goroutine: var total int var mu sync.Mutex var wg sync.WaitGroup for range 100 { wg.Go(func() { if !mu.TryLock() { return } defer mu.Unlock() time.Sleep(time.Millisecond) total++ }) } wg.Wait() total: 1 RWMutex The sync.RWMutex type distinguishes between readers and writers. It provides two sets of methods: Lock / Unlock lock and unlock the mutex for both reading and writing. RLock / RUnlock lock and unlock the mutex for reading only. var total int var mu sync.RWMutex var wg sync.WaitGroup // 10 writers. for range 10 { wg.Go(func() { mu.Lock() defer mu.Unlock() time.Sleep(time.Millisecond) total++ }) } // 10 readers. for range 10 { wg.Go(func() { // Try switching from RLock/RUnlock to Lock/Unlock //and see how it affects the elapsed time. mu.RLock() defer mu.RUnlock() time.Sleep(time.Millisecond) _ = total }) } wg.Wait() elapsed: 10ms Here's how it works: If a goroutine locks the mutex with Lock(), other goroutines will be blocked if they try to use Lock() or RLock(). If a goroutine locks the mutex with RLock(), other goroutines can also lock it with RLock() without being blocked. If at least one goroutine has locked the mutex with RLock(), other goroutines will be blocked if they try to use Lock(). This creates a "single writer, multiple readers" setup. Locker Both sync.Mutex and sync.RWMutex implement the same sync.Locker interface: type Locker interface { Lock() Unlock() } By using Locker instead of a specific mutex type, you can build components that don't depend on a specific lock implementation. This lets the client decide which lock to use. Channel as mutex You can use a channel instead of a mutex to protect shared data: var total int lock := make(chan struct{}, 1) var wg sync.WaitGroup wg.Go(func() { lock <- struct{}{} defer func() { <-lock }() total++ }) wg.Go(func() { lock <- struct{}{} defer func() { <-lock }() total++ }) wg.Wait() total: 2 # Semaphores A semaphore is like a container with N available slots and two operations: acquire to take a slot and release to free a slot. Here are the semaphore rules: Calling acquire takes a free slot. If there are no free slots, acquire blocks the goroutine that called it. Calling release frees up a previously taken slot. If there are any goroutines blocked on acquire when release is called, one of them will immediately take the freed slot and unblock. You can implement a simple semaphore with a buffered channel, where N is the channel's size. To acquire the semaphore, send a value into the channel. To release it, take a value from the channel: // Try changing nConc and see how the elapsed time changes. const nConc = 4 const nCalls = 100 sema := make(chan struct{}, nConc) var wg sync.WaitGroup for range nCalls { sema <- struct{}{} // acquire wg.Go(func() { defer func() { <-sema }() // release time.Sleep(time.Millisecond) // do some work }) } wg.Wait() elapsed: 25ms For more complex situations, use the golang.org/x/sync/semaphore package. Rendezvous A rendezvous lets two goroutines wait for each other: There are two goroutines β€” G1 and G2 β€” and each one can signal that it's ready. If G1 signals but G2 hasn't yet, G1 blocks and waits. If G2 signals but G1 hasn't yet, G2 blocks and waits. When both have signaled, they both unblock and continue running. You can implement a simple rendezvous with a wait group: var rend sync.WaitGroup rend.Add(2) var wg sync.WaitGroup wg.Go(func() { fmt.Println("before rendezvous") rend.Done() rend.Wait() fmt.Println("after rendezvous") }) wg.Go(func() { fmt.Println("before rendezvous") rend.Done() rend.Wait() fmt.Println("after rendezvous") }) wg.Wait() before rendezvous before rendezvous after rendezvous after rendezvous Barrier A barrier is a general case of a rendezvous. It lets N goroutines wait for each other: The barrier has a counter (starting at 0) and a threshold N. Each goroutine that reaches the barrier increases the counter by 1. The barrier blocks any goroutine that reaches it. Once the counter reaches N, the barrier unblocks all waiting goroutines. You can implement a simple barrier with a wait group: const n = 4 var bar sync.WaitGroup bar.Add(n) var wg sync.WaitGroup for range n { wg.Go(func() { fmt.Println("before the barrier") bar.Done() bar.Wait() fmt.Println("after the barrier") }) } wg.Wait() before the barrier before the barrier before the barrier before the barrier after the barrier after the barrier after the barrier after the barrier # Signaling The sync.Cond (conditional variable) type lets one goroutine signal to another that it's ready, and lets the other goroutine wait for that signal. A Cond includes a mutex and has two methods β€” Wait and Signal. Wait unlocks the mutex and suspends the goroutine until it receives a signal. Signal wakes the goroutine that is waiting on Wait. When Wait wakes up, it locks the mutex again. cond := sync.NewCond(&sync.Mutex{}) done := false var wg sync.WaitGroup wg.Go(func() { cond.L.Lock() fmt.Println("G1 is ready to signal") done = true cond.Signal() cond.L.Unlock() }) wg.Go(func() { cond.L.Lock() for !done { cond.Wait() } fmt.Println("G2 received the signal") cond.L.Unlock() }) wg.Wait() G1 is ready to signal G2 received the signal If there are multiple waiting goroutines when Signal is called, only one of them will be resumed. If there are no waiting goroutines, Signal does nothing. You can also use the Broadcast method. While Signal wakes up only one goroutine waiting on Cond.Wait, the Broadcast method wakes up all such goroutines. You can signal with a channel: signal := make(chan struct{}, 1) go func() { // do something signal <- struct{}{} }() go func() { <-signal // do something }() And broadcast too: broadcast := make(chan struct{}) go func() { // do something close(broadcast) }() go func() { <-broadcast // do something }() go func() { <-broadcast // do something }() Broadcasting with a condition variable is limited: it only sends a signal, not the actual data, and it only works once. With channels, you can build a publish/subscribe system that doesn't have these limitations: type Publisher struct { sbox []chan int // subscription channels mu sync.Mutex // protects the state } func (p *Publisher) Subscribe() <-chan int { p.mu.Lock() defer p.mu.Unlock() sub := make(chan int, 1) p.sbox = append(p.sbox, sub) return sub } func (p *Publisher) Broadcast(v int) { p.mu.Lock() defer p.mu.Unlock() for _, sub := range p.sbox { select { case sub <- v: default: } } } # Run once The sync.Once type makes sure that the given function runs only once. If multiple goroutines call Once.Do at the same time, only one will run the function, while the others will wait until it returns: total := 0 initState := func() { total += 1 } var once sync.Once var wg sync.WaitGroup wg.Go(func() { once.Do(initState) // do something }) wg.Go(func() { once.Do(initState) // do something }) wg.Wait() total: 1 Once is perfect for one-time initialization or cleanup in a concurrent environment. Besides the Once type, the sync package also includes three convenience once-functions: // Calls f only once. func (o *Once) Do(f func()) // Returns a function that calls f only once. func OnceFunc(f func()) func() // Returns a function that calls f only once // and returns the value from that first call. func OnceValue T) func() T // Returns a function that calls f only once // and returns the pair of values from that first call. func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) # Object pool

      The sync.Pool type helps reuse memory instead of allocating it every time, which reduces the load on the garbage collector:

      pool := sync.Pool{
          New: func() any {
              buf := make([]byte, 1024)
              return &buf
          },
      }
      
      // Only allocates 4*1024 B, despite 4000 loop iterations.
      var wg sync.WaitGroup
      for range 4 {
          wg.Go(func() {
              for range 1000 {
                  buf := pool.Get().(*[]byte)
                  sink = buf
                  pool.Put(buf)
              }
          })
      }
      wg.Wait()
      
      
      
      Memory allocated: 4 KB
      

      Get takes an item from the pool. If there are no available items, it creates a new one using New (which we have to define ourselves, since the pool doesn't know anything about the items it creates). Put returns an item back to the pool.

      Things to keep in mind:

      • New should return a pointer, not a value, to reduce memory copying and avoid extra allocations.
      • The pool has no size limit. If you start 1000 more goroutines that all call Get at the same time, 1000 more buffers will be allocated.
      • After an item is returned to the pool with Put, you shouldn't use it anymore (since another goroutine might already have taken and started using it).

      # Atomics

      An operation without synchronization can only be truly atomic if it translates to a single processor instruction. Such operations don't need locks and won't cause issues when called concurrently (even the write operations).

      There are only a few atomics, and they're all found in the sync/atomic package:

      Int32     Bool
      Int64     Value
      Uint32    Pointer
      Uint64
      

      Each atomic type provides the following methods:

      • Load reads the value of a variable.
      • Store sets a new value.
      • Swap sets a new value (like Store) and returns the old one.
      • CompareAndSwap sets a new value only if the current value is still what you expect it to be.

        var n atomic.Int32 n.Store(10) swapped := n.CompareAndSwap(10, 42) fmt.Println("CompareAndSwap 10 -> 42:", swapped) fmt.Println("n =", n.Load())

        CompareAndSwap 10 -> 42: true n = 42

      Numeric types also provide an Add method that increments the value by the specified amount.

      All methods are either translated into a single CPU instruction or are otherwise guaranteed to be atomic, so they are safe to use from multiple goroutines.

      The composition of atomics is always non-atomic:

      var delta atomic.Int32
      var counter atomic.Int32
      
      func increment() {
          // Not atomic; causes a race condition.
          delta.Add(1)
          sleep(10)
          counter.Add(delta.Load())
      }
      
      // After 100 concurrent increments,
      // the final value is NOT guaranteed.
      
      
      
      counter = 9386
      

      A bulletproof way to make a composite operation atomic and prevent race conditions is to use a mutex:

      var delta int32
      var counter int32
      var mu sync.Mutex
      
      func increment() {
          // Atomic; doesn't cause a race condition.
          mu.Lock()
          delta += 1
          sleep(10)
          counter += delta
          mu.Unlock()
      }
      
      // After 100 concurrent increments, the final value is guaranteed:
      // counter = 1+2+...+100 = 5050
      
      
      
      counter = 5050
      

      Sometimes you can use an atomic type instead of a mutex to exit early:

      type Gate struct {
          closed atomic.Bool
      }
      
      func (g *Gate) Close() {
          if !g.closed.CompareAndSwap(false, true) {
              return // ignore repeated calls
          }
          // The gate is closed.
          // We can free resources now.
      }
      

      # Testing

      If your concurrent program uses channels or custom types with synchronization methods like Wait, you can use those in your tests. This way, your tests won't be much more complicated than if the code were synchronous:

      // Calc calculates something asynchronously.
      func Calc() <-chan int {
          out := make(chan int, 1)
          go func() {
              out <- 42
          }()
          return out
      }
      
      
      
      func Test(t *testing.T) {
          // Wait for the Calc goroutine to finish.
          got := <-Calc()
          if got != 42 {
              t.Errorf("got: %v; want: 42", got)
          }
      }
      
      
      
      PASS
      

      If there aren't any suitable synchronization "handles" in the code you're testing, you can use the synctest package. It exports two functions:

      func Test(t *testing.T, f func(*testing.T))
      func Wait()
      

      synctest.Test runs an isolated bubble. The bubble uses a fake clock, and you can manually control goroutine synchronization with synctest.Wait.

      synctest.Wait blocks until all goroutines in the bubble β€” except the one that called Wait β€” have either finished or are durably blocked. This lets you wait for a specific goroutine to finish or get blocked, so you can check the program's state:

      // NewProc starts the calculation.
      func NewProc() *Proc {
          p := &Proc{done: make(chan struct{})}
          go func() {
              p.res = 42
              <-p.done // (X)
              p.res = 0
          }()
          return p
      }
      
      
      
      func Test(t *testing.T) {
          synctest.Test(t, func(t *testing.T) {
              p := NewProc()
              defer p.Stop()
      
              // Wait for the goroutine to block at point X.
              synctest.Wait()
              if got := p.Res(); got != 42 {
                  t.Fatalf("got %v, want 42", got)
              }
          })
      }
      
      
      
      PASS
      

      The fake clock in synctest.Test move forward only if: ➊ all goroutines in the bubble are durably blocked; βž‹ there's a future moment when at least one goroutine will unblock; and ➌ synctest.Wait isn't running. Thanks to this, time-dependent tests run instantly:

      // Calc processes a value from the input channel.
      // Times out if no input is received after 3 seconds.
      func Calc(in chan int) (int, error) {
          select {
          case v := <-in:
              return v * 2, nil
          case <-time.After(3 * time.Second):
              return 0, ErrTimeout
          }
      }
      
      
      
      func Test(t *testing.T) {
          synctest.Test(t, func(t *testing.T) {
              ch := make(chan int)
              got, err := Calc(ch) // runs instantly
      
              if err != ErrTimeout {
                  t.Errorf("got: %v; want: %v", err, ErrTimeout)
              }
              if got != 0 {
                  t.Errorf("got: %v; want: 0", got)
              }
          })
      }
      
      
      
      PASS
      

      The following operations durably block a goroutine:

      • A blocking send or receive on a channel created within the bubble.
      • A blocking select statement where every case is a channel created within the bubble.
      • Calling Cond.Wait.
      • Calling WaitGroup.Wait if all WaitGroup.Add calls were made inside the bubble.
      • Calling time.Sleep.

      Blocking on mutexes, I/O, or system calls is not considered durable, and the synctest bubble can't handle them.

      # Scheduling

      At the hardware level, CPU cores are responsible for running parallel tasks.

      At the operating system level, a thread is the basic unit of execution. There are usually many more threads than CPU cores, so the operating system's scheduler decides which threads to run and which ones to pause.

      At the Go runtime level, a goroutine is the basic unit of execution. The runtime scheduler runs a fixed number of OS threads, often one per CPU core. There can be many more goroutines than threads, so the scheduler decides which goroutines to run on the available threads and which ones to pause. The scheduler keeps switching between goroutines to make sure each one gets a turn to run on a thread, instead of waiting in line forever.

        CPU                  OS                   Go runtime
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  run on β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  run on β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚ Cores    β”‚ <────── β”‚ Threads  β”‚ <────── β”‚ Goroutines β”‚
      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      

      This is how Go handles concurrency.

      Goroutine scheduler

      The goroutine scheduler's job is to run M goroutines on N operating system threads, where M can be much larger than N. Here's a very simplified version of it's algorithm:

      • If there's a free thread, assign it a goroutine from the queue.
      • If a running goroutine gets blocked (for example, while reading from a channel), put it back in the queue and assign a different goroutine to the thread.
      • If a running goroutine gets stuck in a syscall, start a new thread to run other goroutines until the blocked goroutine finishes the syscall.
      • Check the running goroutines every 10 ms. Preempt long-running goroutines and return them to the queue to prevent starvation.

        β”Œβ”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β” β”‚ G17 β”‚β”‚ G18 β”‚β”‚ G19 β”‚β”‚ G20 β”‚ queue β””β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”˜

        β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ G15 β”‚ β”‚ G16 β”‚ β”‚ G13 β”‚ β”‚ G14 β”‚ running β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Thread E β”‚ β”‚ Thread F β”‚ β”‚ Thread C β”‚ β”‚ Thread D β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

        β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ G11 β”‚ β”‚ G12 β”‚ syscalls β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Thread A β”‚ β”‚ Thread B β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

      The number of threads running Go code is controlled by the GOMAXPROCS environment variable or the runtime.GOMAXPROCS function.

      A goroutine is a structure that starts out using about 2 KB of memory, mostly for its stack. The stack can grow if needed. Since goroutines are so lightweight, you can run tens of thousands or even hundreds of thousands of them on a small machine.

      # Diagnostics

      To troubleshoot concurrent programs in production, we use metrics, profiling, and tracing.

      Metrics show how the Go runtime is performing, like how much heap memory it uses or how long garbage collection pauses take. Each metric has a unique name and a value, which can be a number or a histogram.

      You can use the runtime/metrics package to get a complete list of metrics or check the values of specific ones:

      samples := []metrics.Sample{
          {Name: "/sched/gomaxprocs:threads"},
          {Name: "/sched/goroutines:goroutines"},
      }
      metrics.Read(samples)
      
      for _, s := range samples {
          fmt.Printf("%s: %v\n", s.Name, s.Value.Uint64())
      }
      
      
      
      /sched/gomaxprocs:threads: 8
      /sched/goroutines:goroutines: 1
      

      In practice, people rarely do this manually. Instead, all metrics are automatically exported using Prometheus or OpenTelemetry libraries.

      Profiling helps you understand exactly what the program is doing, what resources it uses, and where in the code this happens. Go uses a sampling profiler that's suitable for production.

      The most commonly used profiles are CPU, which shows how much processor time each function uses, and heap, which shows how much heap memory each function uses. Goroutine, block, and mutex profiles help identify problems related to concurrency.

      The easiest way to add a profiler to your app is by using the net/http/pprof package. To collect a profile with the given name, call the /debug/pprof/{name} endpoint. To view the collected profile, use the go tool pprof utility:

      go tool pprof -proto \
        "http://localhost:6060/debug/pprof/profile?seconds=N" > cpu.pprof
      go tool pprof -http=localhost:8080 cpu.pprof
      

      You can also profile manually:

      // CPU profile.
      file, _ := os.Create("cpu.prof")
      defer file.Close()
      pprof.StartCPUProfile(file)
      defer pprof.StopCPUProfile()
      // ...
      
      
      
      // Any other profile.
      file, _ := os.Create(name + ".prof")
      defer file.Close()
      pprof.Lookup(name).WriteTo(file, 0)
      

      Tracing records certain types of events while the program is running, mainly those related to concurrency and memory. When the profiling server from the net/http/pprof package is running, call the /debug/pprof/trace endpoint to collect a trace. To view the results, use the go tool trace utility.

      You can also collect a trace manually:

      file, _ := os.Create("trace.out")
      defer file.Close()
      trace.Start(file)
      defer trace.Stop()
      // ...
      

      You can set up automatic tracing with a sliding window that's limited by size or duration. This is called "flight recording". It lets you always keep a recent trace available in case something goes wrong:

      cfg := trace.FlightRecorderConfig{
          MinAge:   5 * time.Second,
          MaxBytes: 3 << 20, // 3MB
      }
      rec := trace.NewFlightRecorder(cfg)
      rec.Start()
      defer rec.Stop()
      

      # Final thoughts

      We've covered a number of Go tools for writing concurrent programs:

      • Goroutines for running concurrent tasks.
      • Channels and select as flexible communication tools.
      • Timers and tickers for working with time.
      • Context for canceling operations.
      • Wait groups for synchronizing goroutines.
      • Mutexes to prevent race conditions.
      • Condition variables for signaling events.
      • Once for safe one-time initialization.
      • Pools to reduce garbage collector load.
      • Atomic operations.

      If you like the book, please recommend it to your friends or colleagues. If you're interested, check out my other books and projects.

      I'm glad you finished the book. Thank you, and I'll see you next time!

    6. πŸ”— Register Spill Joy & Curiosity #101 rss

      Last week I was on Matt Swanson's podcast and we ended up sharing thoughts and vague predictions about programming languages and frameworks. Matt said that he'd be going to Rails World the week after and that he wouldn't be surprised if DHH said "Rails is over." Prescient. DHH didn't exactly say that Rails is over, but, well, some people did call the keynote a "funeral."

      But that shouldn't be surprising, right? The way we've treated languages and frameworks for the past, say, twenty years is at odds with the fact that writing code by hand is on its way out.

      I am not sure how exactly this will play out, but here are some loose thoughts:

      • Frameworks are no longer the biggest developer productivity lever. Agents are a hundred times bigger.

      • Syntax doesn't really matter anymore, as long as agents can write it well.

      • Tool ergonomics don't matter that much either, do they? Previously, I loved that go comes with go build and go test and go run, but now I wouldn't care if those commands were seventeen times longer.

      • But I think shared abstractions are still worth it. A framework gives you a pre-defined way to access a database, to do auth, to divide things into production and development… That's still handy. Not because it would cost tokens to build it myself (won't matter in the future, see below), but because I just don't want to think about it.

      • What will matter a lot in the future: performance characteristics, resource usage, failure modes, observability, debuggability, deployments, rollbacks. And all of that needs to be legible to the agent. I've tried developing something with the often praised Cloudflare Durable Objects, on which you can run JavaScript, and it was a disaster: the agent constantly thought it was writing normal Node.js JavaScript; it didn't know the runtime characteristics of the Durable Objects; it couldn't easily access the logs. In fact, there are no logs that tell you when your code gets evicted and when it gets resumed… You want the opposite to be the case: the agent should know from looking at the codebase how it will be executed and how it can see that.

      • The big force is that we're switching from "I prefer this language because I enjoy working with it" to "I prefer this language because my agent can get great results with it." Now, how would your preferences change if you switched from driving a car to controlling it remotely? You wouldn't care about heated seats and AC, would you? But you'd care about how fast it can brake, I assume.

      • Ecosystems will change. I can't remember the last time I browsed through GitHub to find a library to do a thing. The old NPM credo of "many, many tiny modules" seems even sillier now than it did ten years ago.

      • Sometimes I wonder whether the thing that makes some developers say that agents will change everything and others that they can't write good code is the language they used with the agent. 99% of the code I've had agents write was in TypeScript. Not a language I love, but, hey, who cares? And agents seem great at it. I wonder what my thoughts would be if I still were writing Rust.

      • Then again: I no longer think that "is the language well-represented in the training data?" matters as much as I thought it would. Intelligence generalizes and I've seen agents just crush custom DSLs that have not shown up in any data, ever. We previously said about humans that "if you learned 5 different languages, you kinda know them all" -- maybe that's what's going to happen with agents too? And it kinda makes sense, right? Why wouldn't a frontier model like Astra or Fable be able to use a new language as long as it can run it and have a feedback loop?

      • Very pessimistic on the future of "paper-over" languages and frameworks. You know: this language but with nicer syntax. CoffeeScript, if you're old enough to remember. Haml, Sass, Less -- not sure. What about Elm or ClojureScript? Hmm.

      • A lot of testing frameworks started with TDD in mind: you write a test in which you describe the behavior you want, you run the test to see it fail, you make it pass. Then, with growing adoption of tests, people started using those very same frameworks to add tests after everything already worked. Regression tests. Now we have the very same frameworks being used by agents to write tests god knows how and essentially no one looks at these billions of lines of test code that are generated every day now. Will we still have describe and it blocks in ten years? Just like some terminal emulators still mention baud rates? I do think that the models will get so good that they don't "need" unit tests in the same way humans needed them: to make sure something works. But maybe they won't stop writing them and we'll end up with effectively useless tests piling up?

      • I'd be incredibly surprised if formal methods and strong static typing had the boom that their fans say they will have. Vitamins, not painkillers; worse is better, etc.

      • I have three little apps that I use every day and that I had the agent write, and I have zero clue what language they're written in. I think it's JavaScript?

      • Porting from one language to another seems to be a completely different thing now compared to three years ago.

      Again: I don't know where we'll end up, but I do think being aware of the forces at play is important. It's also fun stuff to think about.

      Before we get to the J &C juice: I'll be in NYC with the Amp team Oct 5-11 and in SF the week after, Oct 12-17. My schedule will be busy and chaotic, but if you're around and want to grab a coffee, let me know!

      • We recorded a new Raising An Agent episode after I said to Quinn: "Man, I'm so full of hot takes today. I need to record a video." So, there it is: an episode full of hot takes. From a whole engineering org making everyone use Qwen, to why GDP isn't increasing if you aren't letting your agents go vertical , to why you're wasting time if you're waiting on your agent instead of the other way around, to why I've been very disappointed by a lot of "software engineers" in the last two years.

      • Expect this to continue: "If we combine all this, we see about 2.5 orders of magnitude decrease in token cost in the last year. Models are about 100x as cost-efficient per-task. Hardware is about 1.3x as energy-efficient per-token. Engines are about 1.4x as energy-efficient per-token." The strongest force in technology today. I stand by what I wrote in last week's predictions about the future of software development: "Tokens are the new computing paradigm. Everything will be re-made on top of it."

      • So, DHH lit the Ruby & Rails world on fire by giving a keynote in which he doesn't talk about Rails all that much. Instead, he said what others (hey , what's up) have said for at least the last six months: writing code by hand is over; these agents are really good; choosing Ruby over other languages due to its developer friendliness doesn't make a lot of sense anymore; old engineering tradeoffs should be revisited. But he said it in a way that only DHH can. He's very, very good at boiling things down to their essence and turning them into statements that make you choose whether you're for or against it. It's impressive, honestly. Read the Hacker News comments to get a taste. Now, predicting the next six months, I wonder when the Omarchy community will run into the question of: wait, so we now have this malleable operating system, but… if agents write and debug all the code, why do we even need it?

      • Thomas Dullien, or: Halvar Flake, gave a presentation: An age of experimentation. It's really good and I highly recommend you click through it. Here, to give you a taste: "Claim: Determinism is dying, and it's unclear how much will remain."

      • New Peter Thiel interview! Say about him what you want (and there's a lot to say), but his ability to read the vibes in the world seems to be rarely matched. What makes this interview also interesting is that the interviewer is Mathias Dopfner, quite the controversial billionaire himself. At some point in the interview, Thiel compares the twenty richest people under 30 in the US with those in Germany and says that the twenty in Germany all inherited their wealth. Guess how Dopfner got his shares of Axel Springer SE? Heavily discounted and some as gifts, from Springer's widow.

      • Attention is all you have: "If, like me and most people, you spend the major part of your day focused on your device, there's no doubt it's affecting you. And when you let someone else dictate what appears on your screen, it's the same as giving them the key to your brain."

      • "But there are pleasures to be had from books beyond being lightly entertained. There is the pleasure of being challenged; the pleasure of feeling one's range and capacities expanding; the pleasure of entering into an unfamiliar world, and being led into empathy with a consciousness very different from one's own; the pleasure of knowing what others have already thought it worth knowing, and entering a larger conversation."

      • Martin Fowler: I don't like LLMs. "I don't like them. They talk to me in this grating LLM-voice, an uncanny valley of talking to a real human. They confidently bullshit me - often giving me useful, helpful answers. But also just making stuff up with the same assurance - and with only a veneer of fake remorse when I call them out on it." He's got a point. Many times a day I think to myself "god, shut UP " when the model comes back with whatever the latest equivalent to "you're absolutely right!" is: spines, seams, belts and braces (or suspenders). But… less and less so? I parse their output more like a receipt I get handed in a shop or in a restaurant: skip the stuff at the top, ignore the gibberish at the bottom, zoom in on the stuff there in the middle.

      • I didn't know about the Moving Image Archive but was delighted when I came across its collection of animated maps.

      • This seems very neat and makes me want to build something with Go: Platform-independent SIMD.

      • "I text him that night and I said, 'Hey, I want to ask you a question. Can I coach you hard?' […] Then the next day he sees me in pre-practice, and I said, 'Just let me explain this. Do you see yourself being in the Hall of Fame someday?' And he said, 'Yeah.' And I said, 'All right. Well, I don't think your trajectory right now is steep enough to make that goal happen. I think your trajectory is five Pro Bowls, a couple All-Pro teams, phenomenal career, all-time leading rusher, but I think your trajectory, and I think your practices have to be…' And then he starts complaining to me, and I said, 'I thought you told me I could coach you hard.' And then, you know, it hit him."

      • How to Unclench. I finally read this after it made a big splash last week. It's a much faster read than I thought it would be. It's like a neat little mini-book.

      • Robert O'Callahan: "I'm resigning from Google today. This has not been an easy decision. I love my colleagues and my work environment, and being paid handsomely to solve fun puzzles has been amazing. But my team's goal is ultimately to make AI much cheaper and lower-latency, and I don't think that's good for people right now: I firmly believe AI progress is currently far too rapid (and I have doubts about the destination too)."

      • "I started to explain how it happened when they cut me off with 'Michael, I don't want the details'." Good stuff.

      • Thomas H. Ptacek and Kurt Mackay are leaving fly.io to build a phone: "Today, almost all software comes from expert strangers. But soon, strangers will stop supplying our apps, and instead ship just their building blocks. Sure, there will still be megaproject browsers and word processors. But there'll be thousands of times more applications that pull in 1/7th of the guts of a word processor to solve some idiosyncratic work or home life problem for somebody who doesn't know what a for-loop is. […] That's what we're working on: a platform that is the device we would want to have in the world I just described. So: we're building a phone." I nod my head to the boldness of entering one of the most competitive markets of all time.

      • It's totally not the point of this Obie Fernandez post, but I can't stop thinking about this part here: "Trying to make a point, I hit enter to accept Fable's first suggestion. Minutes later I hit enter again, and then again, choosing to delete some dead code. We start making a PR. My friend asks me to make sure it's set to draft. Sure, whatever. Fable does its thing. My friend checks the diff. It's a simple deletion of dead code and associated unit tests. I want to push on, but my friend begs me to stop. 'You don't understand Obie, I can't just do what you're doing, man.' I challenge him to explain why not. He explains that he has a boss and teammates and that he can't just make changes like that, he has to present plans and execute on them." It made me remember what it feels like to work in such teams, where you can't make decisions alone, where you're not trusted to make a call like "I'm going to refactor this API" or "I'm going to delete this" or "I'm going to add a new feature that lets us…" without having reached a consensus with the team. Remembering that made me feel sad, thinking: "wow, imagine what it's like to now have AI but no power, no trust, no freedom to use it?" There's no way around it: if you box AI into this little corner where all it can do is change code on your local machine and help you push it up as a PR, you're holding the leash at a fraction of its real size.

      • Intellectuals are F*cking Idiots: "Reality always wins. But Intellectuals are rewarded for their models, not reality. And the data and analysis that looks elegant on paper is often disastrous on the ground. Yet, when their models are contradicted by reality, most intellectuals don't have the courage to accept the reality, instead they double down on their models …and this is what turns them into idiots." If it's nothing else, this was entertaining!

      • I thought of this John Carmack post again, so here it is, again: "Make better decisions and fill your products with 'Give a Damn'!"

      • This is fantastic: Fixing the Portobello Police Station Clock. It's a hack, it's nerdy, it's real blogging. This is the type of stuff that made me fall in love with the Internet.

      Thoughts on the future of programming languages? Subscribe here:

    7. πŸ”— HexRaysSA/plugin-repository commits sync repo: +4 releases, -1 release rss
      sync repo: +4 releases, -1 release
      
      ## New releases
      - [augur](https://github.com/0xdea/augur): 0.10.3
      - [ida-rpc](https://github.com/bkerler/ida_rpc): 0.2.0
      - [patching-ng](https://github.com/mahmoudimus/patching-ng): 0.5.0, 0.4.0
      
      ## Changes
      - [patching-ng](https://github.com/mahmoudimus/patching-ng):
        - host changed: mahmoudimus/patching β†’ mahmoudimus/patching-ng
        - removed version(s): 0.3.0
      
    8. πŸ”— OmniNull/OmniWM OmniWM v0.7.3 release

      What's New Since 0.7.2

      The command palette can now launch apps and find files, and the workspace bar can organize windows directly. OmniWM 0.7.3 also adds translations for 20 locales and improves window visibility, input, and wallpaper previews.

      Command palette and language support

      • Browse apps and files. Applications mode searches installed apps; Files mode searches the macOS metadata index and shows recent documents before you type. Open a result, reveal it in Finder, or preview a file from the keyboard.
      • More useful commands and clipboard history. The palette now lists layout-aware OmniWM commands, including commands without shortcuts. Clipboard mode adds previews, pins, copy-first selection, paste options, and history that survives quitting. Clipboard history remains off until you enable it.
      • Use OmniWM in your macOS app language. The desktop app, commands, permissions, and workspace bar controls have translations for 20 locales. Command search recognizes translated and English terms; configuration and CLI syntax remain English.

      Workspace and window behavior

      • Arrange windows from the bar. Drag icons to reorder Niri columns, stack windows, swap Dwindle tiles, or move a window to a precise position in another workspace. Context menus add actions for workspaces, windows, and scratchpad pills; Shift-click moves the focused window. Hover over a window icon for a preview when capture is available.
      • Input stays predictable. One discrete mouse-wheel notch moves one Niri column and focuses its window; trackpad scrolling is unchanged. Clicking a workspace bar icon or Niri tab keeps the pointer in place, and Focus Follows Mouse pauses during macOS screenshot selection.
      • Window visibility recovers more reliably. OmniWM restores parked windows when quitting or disabling it, removes unused dynamic workspaces after transitions, and keeps hidden windows parked after size changes or late frame writes.
      • Wallpaper previews show the desktop you chose. Overview and workspace swipes capture rendered solid-color wallpapers when Screen Recording is available, with an image-file fallback and recovery after a failed capture.

      Diagnostics and compatibility

      • Diagnostic captures now separate event queue wait from handling time and identify window and WindowServer work. Fish completion no longer suggests filenames after omniwmctl arguments.
      • No configuration or direct IPC migration is required for this release.

      Thanks

      Thank you to Jonathan Macheret for mouse-wheel navigation and focus, and Richard Ginzburg for Fish completion and test fixture fixes and wallpaper preview fixes. Both contributors are now credited in the README and website, with Jonathan's Liip affiliation.

      @Guria for ideas from OmniWM's Nehir fork

      Full changelog: v0.7.2…v0.7.3

      Website and documentation Β· Installation guide

      Release Integrity

      The app is signed, notarized, and stapled. SHA-256 hashes:

      • OmniWM-v0.7.3.zip: bb99c3a16ca7178e5ae80f84377c7923cb4ffa3642d80d461571b1953d50f5df
      • GhosttyKit.xcframework-v0.7.3.zip: 8b3852de8ab87c4664af0aeb5c74f7bef46fbc0aff8f53ebc0ce120c84605c08
  3. September 25, 2026
    1. πŸ”— r/Harrogate Hedge & tree recommendations rss

      Can anyone suggest reliable, responsive and available hedge and tree care people? I just moved to the area and need to line someone up for the annual hedge trim, but have reached out to three businesses so far and none have even replied

      submitted by /u/LittleSadRufus
      [link] [comments]

    2. πŸ”— Mr. Money Mustache Will the AI Bubble Destroy our Retirement? rss

      Wow, how about that stock market?

      It’s a phrase we keep having to dust off and use again, as the years go by and the market keeps surprising us.

      When it crashes, some of us worry because we see our retirement stash shrinking.

      But even when it rises to record levels and then to super-duper-crazy record valuations, we find reason to worry. Because that just means an even bigger crash is coming, right? Especially when this boom-bubble is built on the back of something as frenzied as the present Artificial Intelligence boom… right?

      For those who have been happily tuned out of this drama and just enjoying life: Congratulations! Keep up the great work. But just to give you a quick background for the purposes of this article, here’s the AI story in three points:

      • Over the past few years, AI has reached a level where it can do complex thinking and reasoning tasks that most of us thought might not happen in our lifetimes. It’s shockingly useful.
      • This has led to the fastest worldwide adoption of any technology in history: over 1.5 billion people are already using it, as are most large companies
      • And this has caused a crazy cycle of growing sales, investor enthusiasm and data center buildout that is now the largest investment cycle humans have ever made in anything: 1.8 trillion dollars has been spent since just 2022, with another trillion going out the door next year alone. Which you can compare to:

      • The cost of the 2700-foot-tall Burj Khalifa, the highest skyscraper in the world (about $2 billion in today’s dollars)

      • Or the entire US interstate highway system, 48,000 miles of wide, flat, highly engineered roads (and 55,000 massive bridges) which cross countless mountain ranges, rivers and canyons: $660 billion in today’s dollars.

      So where’s the problem?

      Investors worry that while AI is definitely a major invention, the mania around it is still too much, too fast, and thus we are due for an even bigger version of the dot com crash we had in the early 2000s. Which happens to be an interesting subject for me, since that was the boom that boosted my own early career and got me on the path to early retirement … before the ensuing crash almost cost me my job and my citizenship application.

      As an aging Internet Financial Guru, I have the privilege of getting lots of questions about this investment cycle, just as I have about past booms and busts. And to address them, I decided to not only write this blog article but also create a little talk about it - which I already gave for the first time at an event called Camp FI Midwest earlier this month. I also hope to polish it up and deliver an improved version at the Bogleheads conference in November.

      So what that means is that today you not only get a blog post, but a few silly presentation slides to go with it for extra entertainment. All with no need for a plane ticket or all that hassle of leaving your house. So let’s get into it!

      Will the AI Bubble Ruin our Retirement?

      -

      So I recently hit 21 years of retirement. This means I’ve grown pretty comfortable with the idea. But it still threw me for a loop one time when a friend asked me this question:

      β€œHow can you be retired, and comfortable in your retirement, and sleep at night, with everything that’s going on in the world? Aren’t you worried?”

      And I was like, β€œNo, what should I be worried about?”

      But if you’re a news watcher and a worrier yourself, you can probably think of a few things I should be worried about. Perhaps stuff like this:

      2026 worries

      You’ve got our corrupt and/or dangerous politicians like Trump and Putin menacing the world. Inflation eroding our purchasing power, the AI Bubble, the towering US National Debt, the Iran war and the Ukraine war and all these other wars that are just on the verge of tipping us all into destruction.

      But it turns out these were not the things my friend was asking about.

      Because she actually asked me this question over twenty years ago… In the year 2006. So back then we were worried about entirely different things, right?

      2006 worries

      Back then we had corrupt and/or dangerous leaders like Bush, Hussein and of course Bin Laden. There was still a war in the middle east but it was Iraq instead of Iran. We were still worried about the National Debt and Inflation. And we also worried about our overvalued stock market. But back then it was because of the housing bubble instead of the AI bubble. And instead of AI taking our jobs it was going to be outsourcing to India and China.

      Oh and here’s an interesting one: There was a big debate over Peak Oil, and whether the world was doomed because fossil fuel use was always increasing while supply was bound to decrease. And it turns out the opposite happened as you'll see below.

      So then after all this worry in 2006, what ended up happening?

      -

      Well we did get the Great Financial Crisis, which was caused by a combination of irrational exuberance over increasing house prices combined with a foolish degree of fancy leverage in the financial instruments used to issue mortgages. And it was the biggest crash since the Great Depression.

      But even in that craziest of situations, look how minor it looks in the big picture. A $100,000 investment still ended up ballooning into $852,000 today. And if you look carefully in here you can just see the Covid Crash tucked in there in 2020. Remember that?

      And not only that, the rest of the world got a lot better too:

      -

      The portion of people living in extreme poverty dropped from 20% in 2005 to only 8% over the next 20 years. Infant mortality - children who die in their first year of life - was cut in half. Clean energy became a thing as solar panels became about 40 times cheaper. So solar generation now makes up the vast majority of all the new generation we add today (the equivalent of 647 nuclear power plants of peak capacity added last year, but with much cheaper and easier solar panels). And sales of pure electric cars have gone from zero up to about a quarter of all the cars sold on Earth last year, on its way to 100%, which is where they already are in Norway.

      So if we circle back to that question my friend asked me 20 years ago: do you think I should have focused my energy on worrying about an uncertain future, or optimism?

      Which of course brings up the question: Is it different this time?

      Maybe the US national debt is finally big enough to really start messing with us. Maybe the AI bubble is way bigger than the housing bubble and we won’t bounce back from this next stock market crash. Or maybe AI will spin out of control into a super intelligence that takes over the planet and realizes it does not need us any more.

      And the financial media loves to spin a good scary story about all of this. But you know what the fear mongers all seem to be missing?

      It’s the fact that at the core, our prosperity does not come from the financial system or the stock market, which are just some made-up numbers on some computers.

      Really, Prosperity comes from Productivity.

      -

      We first covered this lesson right here on MMM, in the 2014 Classic entitled, β€œWhy we are not really all doomed”. And sure enough, twelve years later the timeless lessons therein remain just as true now as they were back then.

      And the lesson is really simple: the world of capitalism is always choppy and prone to wild swings. But if you peek beneath the waves, there are real people in there, doing real work and coming up with real inventions.

      In fact, the very idea of an β€œeconomy” or a financial system is just another human invention. If the whole system crashes because we don’t like the numbers we see in there, we can literally make up some new numbers and then get back to work. Which is exactly what we did in 2008 and many times before that, and it seems to have worked just fine.

      Meanwhile, we still have every tool we ever invented to boost our productivity. And our standard of living, and our economic growth, and our possibility of retiring early, all come from high productivity.

      But even Productivity and Prosperity aren’t all that important.

      Because once we reach a certain standard of living, human happiness kind of tops out in that dimension. In first-world countries, we blew past those minimum requirements several decades ago. And then we start looking for other factors to maximize our overall happiness. All anybody really wants is to lead the happiest, most satisfying life they can manage

      And when you think of it that way, your wealth is really just part of your life situation, which is part of this little green slice of the happiness pie.

      Approximate non-scientific factors in happiness

      Genetics is unfortunately the biggest slice - some people are just plain happier than others. But there are also quite a few choices that are within your control.

      Focusing on good close relationships and being kind to people. Practicing Gratitude and Optimism as your lifelong philosophy. And making sure you pack as many healthy (outdoor) activities into your days as you can.

      So, I didn’t invent anything new in this blog post. And most of it is just a repackaging of the same stuff I’ve been writing about since 2011. And yet some regular MMM readers who presumably know all this stuff are STILL afraid. Afraid to retire, afraid of things in their unknown future. Can we fix this?

      I think the biggest problem is Fear Itself.

      -

      The problem is that as a Human Being, you are basically a Danger Detection Machine. And this pervasive background fear is just a trait we evolved to protect ourselves from danger.

      But there’s a weird thing about our detection machinery. We can adapt and learn to function even in very dangerous environments, but when the danger goes away, we keep looking for stuff to worry about. In other words, there is a problem with fear: for many of us, it never really goes away. It just keeps moving the goalposts.

      While Nature has wisely endowed us with a fear of predators and disease, if you solve those things you’ll just start worrying about whether or not you can get enough food. And supplies, and protecting yourself against scarcity.

      If you succeed, next you’ll be worrying about if you can fit in with your tribe, because if you don’t, you might be exiled.

      And if you don’t have to worry about that, you’ll go back to worrying about money for different reasons. Thanks to our tendency to use comparisons rather than absolutes (sometimes called "anchoring bias" and the "contrast effect") when judging our lives, just getting out of poverty isn’t enough. We just move on to wanting more money.

      And then, when some people get enough money to have a comfortable upper- middle-class lifestyle, they take the logical next step of starting a Homeowner's Association, so they can worry about the unauthorized weeds on their neighbor’s lawn. Or maybe a Political Action Committee in hopes of controlling the color of their future neighbors' skin.

      And even if all the lawns and gardens are green and perfect and people get really rich, they just move on to worry about tax strategy, leaving a huge inheritance for their children, and avoiding RMDs.

      Required minimum Distributions. This is when, if you get to 72 years old and you have too much money , the government will start making you withdraw some of that money so you can spend it and pay taxes on it. And people actually worry about this. I shit you not.

      But wait! You can actually fight back against fear. It’s just a bit tricky because it requires some self awareness. But you have the advantage, because you have the ability to think rationally. And your fear is quite clearly irrational.

      -

      And the first step is to understand your own history. Are you afraid of certain things because of your childhood?

      I might have been afraid of running out of money, because in my family there was always a perceived shortage of the stuff, possibly enforced by my Dad’s scarcity mentality. And he was aware that his own fears of scarcity came from childhood as well: he grew up in a truly low income family in the 1940s and 50s, under the care of parents who had lived through the Great Depression of the 1930s.

      Even more significant is that survivors of trauma and abuse will very likely end up with fear issues in adulthood. It’s natural, and unfortunately abuse is way more widespread even in this country than we would like to admit. This makes it harder to eliminate, but it still helps to understand that your past is what is causing these feelings, rather than an objective understanding of the present.

      Identify the Fear: One you understand yourself, it helps to talk through the nature of your fear in more detail. This is also often taught in therapy. What would happen if it really came true? What would the worst case scenario be? It’s often not all that bad.

      In one post, why you’ll probably never run out of money, I went to great lengths to imagine what it would mean for a wealthy person like yourself to actually let the well run dry, and let’s say you’ll probably never come close.

      Once you identify the fear, you can start taking action. And one of the best and most accurate slogans ever is that action cures fear. It’s because it makes the unfamiliar, familiar. And you get confidence that you can really create change. And then that learning creates Resilience - the ability to adapt to ANY new situation, which is a trait also known as Badassity. And with sufficient Badassity, nothing is scary.

      -

      Think about it: if you had the skills and abilities to handle ANY situation, skills in the mental and physical and even the realms of emotion and wisdom, would you really have to worry about anything?

      The next thing you can do is tune out all the unnecessary crap from your daily mental diet. And for most Americans, this means the daily news.

      The news is not helping you to keep informed, it’s just poisoning your mind with a hand-picked selection of the scariest stories of each day. If you want to learn about something, go find a book on the subject. And if you want to make a difference, the only thing that counts is not the news stories you watch - it's only the actions that you take - in other words, your Positive Behaviors.

      As a human being, you are wired to solve problems with both your body and your mind, and you are meant to do it outdoors. So if you spend all your days in a house and a car and in an office using the computer, of course you are going to have some mental health problems. This should not be a surprising thing you need to ask your therapist about, it is an expected result of not living the life that you were literally created to live as a Human being!

      So you need to focus on learning, moving, and solving problems. And you need your food and drink intake to be in alignment with all of this too. Life will get a lot less scary as soon as you are living life in the way you were built to live it.

      Then finally, you can start swapping out the fear-mongers in your life, for other positive, productive, optimistic people. And watch how much the positive mentality rubs off on you. In the FI community, at least the subgroup of people that I like to spend time with, nobody focuses on fear. It’s all just about living the best life we can, and helping other people do so as well.

      So now that we know how to fight back against fear, we can start using a new approach when we go into anything new. And that approach is something I like to call Everything is an Experiment.

      Everything is an Experiment

      And this applies whether you are talking about something tiny like a new paint color. But it scales up to bigger things like switching to a new car, or a new house, meeting new friends, looking for new love by going through the sometimes hell of first dates, a new job, or even quitting your last job by taking an early retirement.

      So let's bring this back around to AI, the thing that everybody seems to be afraid of right now.

      I think of our new AI era as just being another giant experiment. And because of this, I don’t find it even remotely scary. But I do find it endlessly fascinating.

      First of all, it’s a change. A potentially unknown one, and for some people that’s scary.

      But instead of just leaving it there and then doom-scrolling ominous news articles about it, why not learn why people are so excited about AI? As someone with a lifelong background in technology and economics (and a general lack of political affiliation), I can see a lot more potential upside than downside. And that mainly comes from the fact that I see AI as just a very fancy form of Cognitive Nail Gun.

      -

      It will boost our efficiency, which means our productivity, which means prosperity. Because it’s really just a super smart brain that we all get to tap into at a fairly negligible cost.

      Unfortunately, it will also displace workers like every other new productivity technology. It’s already doing so in things like entry level office jobs in software and finance. It will eventually replace car and truck drivers, and so on. But in exchange, these services will become cheaper, and new industries will be created because of the new technology. Much like the Internet itself, and then the enormous industries unleashed by smartphones and mobile apps.

      And one unfortunate side effect of these new inventions is that they tend to concentrate their rewards on the people who own them. If you own a house building company and employ a bunch of carpenters and give nail guns to the best of those carpenters, you can now lay off the other half and still get more done. If you own a company and roll out AI to the workers, you can do the same thing.

      And if you’re a big public company, your shareholders also benefit, which happens to include most of the people reading this. As you’ve seen when looking at your portfolios over the past two years.

      Artificial Intelligence is just like any other new thing that pops into your life: it's an opportunity to make some choices. And it is up to you to decide if this is something to be afraid of, or to use as a trigger for learning, action, and living a more interesting life.

      So that's my little talk (and surprisingly long blog post) on Fear versus the AI bubble.

      I hope that if you do feel fearful about this or any other issue in your future life, you'll take it as a cue to learn more about your own fear and emotions and manage them as the root cause they are, rather than just trying to avoid the symptoms. Because in the wise words of my friends The Donegans:

      -

      Everything you want in life, that you don 't already have,
      lies on the other side of Fear.
      Because if you weren't afraid to go out there and get it, you would already have it.

    3. πŸ”— Hex-Rays Blog Hex-Rays IDA MCP Server rss

      Hex-Rays IDA MCP Server

      We are happy to introduce the official IDA MCP server! This free and open source software connects your IDA installation with AI agents, enabling them to disassemble, decompile, and support reverse engineering tasks. It works great with models like Gemini, Qwen, or Opus using familiar harnesses like Claude Code, Codex, or Pi. Across our internal malware analysis-focused benchmark, IDA MCP’s β€œcode mode” architecture reduced token consumption by approximately 20% compared to popular alternatives.

    4. πŸ”— 3Blue1Brown (YouTube) The Phone Number puzzle rss

      Part of a monthly series of puzzles: https://momath.org/mindbenders/

    5. πŸ”— pydantic/monty v1.0.0 - 2026-09-25 release

      What's Changed

      New Contributors

      Full Changelog : v0.0.23...v1.0.0

    6. πŸ”— HexRaysSA/plugin-repository commits sync repo: +1 plugin, +5 releases rss
      sync repo: +1 plugin, +5 releases
      
      ## New plugins
      - [patching-ng](https://github.com/mahmoudimus/patching) (0.3.0)
      
      ## New releases
      - [augur](https://github.com/0xdea/augur): 0.10.2
      - [ida-mcp](https://github.com/hexrayssa/ida-mcp): 20260924.0.3, 20260924.0.2, 20260924.0.1
      
    7. πŸ”— Filip Filmar Fuchsia Internals, Vol. II: The Build System and Toolchains rss

      Volume II of the Fuchsia Internals series takes on the part of the system that is widely regarded as opaque on first contact: the build. The opacity has one principal cause: the multi-toolchain model, in which a single source target may be compiled many times, once per toolchain context, each producing distinct outputs. Once that idea clicks, the rest follows. The full PDF is at the bottom.

      A caveat on these reports: they are auto-generated, so take the specifics with a grain of salt. In my own reading they hold up well and read as generally correct, but verify against the source before you rely on any one detail.

    8. πŸ”— New Music Releases Philip Glass - The Hours (from "The Hours") rss

      Philip Glass - a new release is available:

      • 2026-09-25: The Hours (from "The Hours") (Single)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    9. πŸ”— New Music Releases Max Richter - While the Flood Rages rss

      Max Richter - a new release is available:

      • 2026-09-25: While the Flood Rages (Single)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    10. πŸ”— New Music Releases Linkin Park - Unshatter Film Soundtrack (Live in SΓ£o Paulo) rss

      Linkin Park - a new release is available:

      • 2026-09-25: Unshatter Film Soundtrack (Live in SΓ£o Paulo) (Soundtrack)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    11. πŸ”— New Music Releases Armin van Buuren - Take Me Home rss

      Armin van Buuren - a new release is available:

      • 2026-09-25: Take Me Home (Single)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    12. πŸ”— New Music Releases Faithless - We Come 1 rss

      Faithless - a new release is available:

      • 2026-09-25: We Come 1 (Single)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    13. πŸ”— New Music Releases Kaskade - ORIGIN // rss

      Kaskade - a new release is available:

      • 2026-09-25: ORIGIN // (Album)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    14. πŸ”— New Music Releases The Ocean - Solaris rss

      The Ocean - a new release is available:

      • 2026-09-25: Solaris (Album)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    15. πŸ”— Ampcode News Less Noise rss

      Amp now collapses your agent's step-by-step work even more, since you care about what the agent did, not how it got there. (You can still expand the steps when needed.)

      A year ago, watching your agent's every step sometimes helped, and you probably were only running one agent.

      Now? You have lots of agents. You should be giving them harder, deeper work and verification demands. They should run for longer on their own, without you watching them from up close. If you have the patience to watch your agents work, you're giving them too short a leash.

      Ten separate rows of commands, reads, searches, and edits become one expandable summary. Both are followed by the same assistant reply, fading at the second line.

  4. September 24, 2026
    1. πŸ”— navidrome/navidrome v0.64.2 release

      This is a bug fix release. The main fix is for users on slow storage (USB disks, remote drives), who have seen floods of database is locked errors, UI freezes and failed full scans since 0.64.0. Scans now cause much less lock contention: the artwork worker pauses while a scan runs, and ANALYZE no longer holds the write lock for a long time. It also fixes scans that always failed on 32-bit builds (armv5/6/7, 386) when a file had a broken track or disc number.

      Security

      • Sanitize user-controlled names (playlists, albums, artists, track titles) in the Content-Disposition header of downloads, so a crafted name cannot change the name of the downloaded file. (#5895 by @zapisanchez)
      • Stop writing the admin password to the log when the initial admin user cannot be created. (#5897 by @zapisanchez)

      Scanner

      • Fix database is locked errors, UI freezes and failed scans on slow storage. The artwork worker now pauses during scans, ANALYZE runs one index at a time, @eaDir thumbnail folders are ignored, and folder saves are retried when the database is busy. (#6201 by @deluan)
      • Fix scans failing with value out of range on 32-bit builds when a file has an invalid track number, disc number or BPM. A migration resets existing invalid values. (#6202 by @deluan)

      Server

      • Fix a failed initial admin creation that marked the initial setup as done, leaving the server without an admin user. Also fix invalid JSON in some delete responses and the missing Content-Type on shared playlist (M3U) downloads. (#5897 by @zapisanchez)

      Jellyfin API

      • Honor the IsPublic flag when creating a playlist. Before, all playlists created through the Jellyfin API were private. (#6204 by @deluan)

      New Contributors

      Full Changelog : v0.64.1...v0.64.2

      Helping out

      This release is only possible thanks to the support of some awesome people!

      Want to be one of them?
      You can sponsor, pay me a Ko- fi, or contribute with code.

      Where to go next?

    2. πŸ”— HexRaysSA/ida-mcp v20260924.0.3 release
    3. πŸ”— backnotprop/plannotator v0.27.20 release

      Follow @plannotator on X for updates

      Missed recent releases? Release | Highlights
      ---|---
      v0.27.19 | Before/After image previews in code review, file comments as GitHub file threads, forge-correct #123 links, /plannotator-last finds the right session
      v0.27.18 | Model pickers from your installed Claude and Codex (Opus 5.5, Fable 5.1, GPT-6), unsent PR review comments survive new pushes
      v0.27.17 | Diagram files open in the diagram viewer, OpenCode switches model with agent, idle review stops polling the git remote, Tree is the default review view
      v0.27.16 | Themed diagrams on Mermaid 12, comment on any node or edge, patch-file review, embedded HTML documents render
      v0.27.15 | Plannotator TUI and Herdr Annotate announcement, element context on pinpoints, HTML links open as linked documents, All files panel, Classic diff default
      v0.27.14 | Pi plan progress survives compaction, Codex threads across rollout files, WSL browser setting, Mod+E edit mode
      v0.27.13 | Open a review on a specific base (--base, --diff-type), symlink containment on /api/doc, CI flake fix, Amp decision relay
      v0.27.12 | Unified decision control, token hover cards, local-vs-remote diff, approval notes
      v0.27.11 | OpenCode server leak fix, durable local feedback archive, unknown-subcommand fix
      v0.27.10 | Auto-viewed files on scroll, annotation undo/redo, OpenCode 2 slash commands restored, npm 12 agent terminal fix
      v0.27.9 | WebMCP browser-agent tools, HTML refresh from disk, host seams, lazy renderers, Windows uninstall fix
      v0.27.8 | Pi keeps its prompt cache across plan transitions, thumbs-up returns to HTML annotation, embed picker seam

      What's New in v0.27.20

      Six pull requests, two of them from first-time contributors. Plannotator now supports Mistral Vibe, annotate sessions get the same Options menu and Settings as plan review, jj reviews get the Commits panel, and long lines in plan code blocks wrap. The installer also stops printing sections for tools you do not have.

      Mistral Vibe support

      Mistral Vibe is now a first-class host. On macOS and Linux, the installer detects Vibe when its home folder exists and adds a plan-review hook on Vibe's exit_plan_mode, so plans open in Plannotator for approval or feedback the way they do for Claude Code and Codex. Vibe's tool sends no plan in its payload, so the hook reads the plan the session just wrote from its transcript, and falls back to the newest plan in $VIBE_HOME/plans only within a short window. The installer also adds /plannotator-review, /plannotator-annotate and /plannotator-last skills for Vibe, and plannotator uninstall removes everything it added while leaving your own Vibe hooks in place.

      On Windows, the installers do not configure the hook and do not install the Vibe-specific skills, because Vibe on Windows may run commands through PowerShell. Review and annotate still work there through the shared skills; /plannotator-last is not supported for Vibe on Windows yet. Nothing changes for anyone who does not use Vibe: detection only reacts to Vibe's own hook payload, and the installer only touches Vibe when its home folder already exists. Opt out with --skip-vibe, PLANNOTATOR_SKIP_VIBE_INSTALL=1, or { "skipInstall": { "vibe": true } }.

      (#1480, by @Djiit)

      Annotate gets the full Options menu and Settings

      Annotate and plan review are the same app, but annotate sessions were missing parts of the Options menu and several Settings tabs. They now match plan review: Copy agent instructions, the Display settings, Default Save Action, quick labels, and the Obsidian, Bear and Octarine settings all appear in annotate. Only the settings tied to a plan decision stay plan-only: Save Plans, Hooks, Permission Mode and the Archive tab.

      Copy agent instructions in annotate hands an agent instructions written for documents rather than plans, including how to comment on HTML elements, diagram nodes and documents in a folder session, so an agent can post review comments into a live annotate session. Two controls that appeared but did nothing are gone: Agent Switching in annotate, and "Save to Obsidian/Bear" on HTML, live-app and empty folder sessions, which have no text to save. The Obsidian, Bear and Octarine switches are shared with plan review, and annotate now says so: turning one on also saves each approved plan there.

      (#1602)

      Commits panel for jj reviews

      The Tree | Git status | Commits toggle only offered Commits for plain git reviews, so jj users, including repos where jj and git live side by side, never saw it. jj reviews now get the same panel: your line of work from the working copy back to where it split from trunk, following first parents, with an @ marker on the working copy and jj's short change ids on each row. Clicking a revision shows only that revision's changes against its first parent, headed by its description, with hunk expansion and image previews working as usual. If you save a file while viewing your working copy, the view shows "Diff out of date" and Refresh opens the current version. This needs jj 0.33 or newer; older versions get a clear message.

      (#1604)

      Long lines wrap in plan code blocks

      A long line in a plan's code block ran off the edge of the card and had to be scrolled sideways. Code blocks now wrap long lines, including long URLs and hashes. Blocks laid out as a grid, such as ASCII box diagrams and space- or tab-aligned tables, keep their layout and scroll as before, since wrapping would break their alignment. Copying a code block still copies the original lines.

      (#1603, by @paulmelero)

      Additional Changes

      • Installer only shows the tools you have. The closing summary of install.sh, install.ps1 and install.cmd now prints the Pi, Gemini, Codex, Kiro and Vibe sections only when that tool is detected. A detected tool you chose to skip still reports "detected, skipped". What gets installed is unchanged. (#1606)
      • Guided Review prompt available in@plannotator/core. The guide prompt, its output schema, the output validator and the user-message builder moved into @plannotator/core (0.25.6) so other hosts can generate guides with the exact same prompt. Plannotator's own behavior is byte-for-byte unchanged. (#1605)

      Install / Update

      macOS / Linux:

      curl -fsSL https://plannotator.ai/install.sh | bash
      

      Windows:

      irm https://plannotator.ai/install.ps1 | iex
      

      Claude Code Plugin: Run /plugin in Claude Code, find plannotator , and click "Update now".

      Pi: Update @plannotator/pi-extension to 0.27.20 and restart Pi.

      OpenCode: Clear cache and restart:

      rm -rf ~/.bun/install/cache/@plannotator
      

      What's Changed

      • fix(annotate): plan-review Options menu and Settings parity in annotate by @backnotprop in #1602
      • fix(editor): wrap long lines in plan code blocks by @paulmelero in #1603
      • feat(review): Commits panel for jj sessions by @backnotprop in #1604
      • core: move guide prompt, schema, user message and validator into @plannotator/core by @backnotprop in #1605
      • feat(vibe): add first-class Mistral Vibe support by @Djiit in #1480
      • installer: print agent closing sections only for detected agents by @backnotprop in #1606

      New Contributors

      Contributors

      @Djiit built Mistral Vibe support end to end: origin detection, the plan-review hook that finds the plan a session just wrote, Vibe skills, installer and uninstaller wiring, and tests. They worked through two rounds of review, checked the upstream Vibe source themselves, and kept the branch current with main. We added a few follow-ups on top before merging.

      @paulmelero fixed long code lines overflowing plan code blocks, with before and after videos that made the problem easy to see. We extended it so diagrams and aligned tables keep their layout.

      Full Changelog : v0.27.19...v0.27.20

    4. πŸ”— The Pragmatic Engineer The Pulse: a new trend of CPU shortages rss

      Hi, this is Gergely with a bonus, free issue of the Pragmatic Engineer Newsletter. In every issue, I cover Big Tech and startups through the lens of senior engineers and engineering leaders. Today, we cover one out of four topics from a past issue of The Pulse . Full subscribers received the article below fourteen days ago. If you 've been forwarded this email, you can subscribe here .

      I was at dinner with a bunch of CTOs and Head of Infrastructure folks recently, and from the conversation it was clear that many companies are struggling to source CPUs in the current climate, and are coming to terms with the end of juicy discounts from cloud providers for machines in the new era of surging demand fueled by AI.

      The 'memory crisis' afflicting sectors like video gaming is well established and has been extensively covered in terms of shortages of GPUs, but now it seems like things are just as hard for businesses in need of CPUs from cloud providers.

      In a sign of how things are changing, the disappearance of CPU spot pricing was mentioned at the table. Customers used to be able to pay up to 90% less than the standard price for CPUs, as cloud providers slashed CPU prices for machines that were lying dormant and unused. But that's no longer the case. It seems that spot pricing has vanished because there's no longer any lack of demand for CPUs - quite the opposite.

      I was surprised, but a lot of people chimed in; apparently, it's now nearly impossible to get CPUs on spot instances without long-running connections with cloud providers. Also, reserving specific CPUs now needs to be done months in advance, and cloud providers will even turn down certain reservations because they don't have enough CPUs or the right type of CPUs.

      Even big players struggle to reserve CPUs

      I have asked turbopuffer CEO Simon Eskildsen about their experience of CPU availability in the cloud, since turbopuffer, as a product, runs on CPUs, not GPUs. They operate in AWS, GCP, and Azure, so I asked how easy it is to get CPUs these days. Simon's response:

      "Getting CPUs is not easy anymore. As Reinforcement Learning (RL) is becoming a large amount of the workloads: RL needs a lot of CPUs. So the labs are sucking up a lot of CPUs. During RL, they need to teach the models how to do things, like searching, and then they need the model to run software, which then takes CPUs to run.

      Then, outside of RL, agents need to do all kinds of very general purpose things on a CPU. So as the demand curve is shifting to general purpose agents, CPU demand is also going up.

      Even the big companies are fighting each other for the right to get the CPU allocations. I would assume that it gets a lot worse before it gets better on the CPU side."

      I was able to confirm what Simon said about larger companies struggling; a VP of Engineering at a large inference provider told me they are at the limit on how much GPU and CPU capacity they can buy from their cloud providers. They have cash to spend and want to rent more capacity, and are willing to accept the longest leases. Despite that, cloud providers tell them no more is available!

      AI hogging CPUs

      Katelyn Lesse, Head of Platform Engineering for Claude Platform, has written about the reasons for the massive CPU demand increase:

      "In the past few years, AI-fueled demand has skyrocketed, and these few companies suddenly needed multiple years and tens of billions of dollars to actually add enough capacity. We ended up with 3 separate bottlenecks in factory capacity that AI is exacerbating. At TSMC, GPUs are competing with CPUs (and with Apple, Qualcomm, and Broadcom) for production lines. And at SK Hynix, Samsung, and Micron, HBM [High Bandwidth Memory] is competing with regular DRAM for wafers.

      What we've ended up with is CPUs getting squeezed from both sides. AMD doesn't own fabs [semiconductor fabrication plants], so its CPUs need to come out of TSMC's constrained allocation. Intel does own fabs, but it's been working through yield problems and is now pulling some of its capacity from PC chips in order to make more server chips. And CPUs need DRAM which has gotten more expensive because memory production has shifted toward HBM. Analysts are expecting CPU supply to add more comfortable headroom before memory does, but their expectation is that it's still going to be multiple quarters away."

      AI-fueled demand does increase CPU load, as shown in this graph from Uber, displaying the growth in agent requests over the past six months:

      altNinefold increase in agentic requests over six months. Source:Uber

      Increasingly, "agent requests" not only generate code which is inference-heavy - and therefore needs GPUs - but they also run tools that compile the code, run tests, run linters, and all of this is CPU-heavy. At companies like Uber, Ramp, and others, AI agents no longer run on the dev's local machine, but on a dedicated instance in the cloud. So, the companies reserve more CPUs on their respective cloud providers for agentic workloads. We recently covered how Ramp built and runs its cloud agent, Inspect.

      Basically, the problem is:

      • AI applications use more and more CPUs, thanks to agents running a lot more software. AI data centers used to have a ratio of 1 CPU to 8 GPUs. Now the ratio is more 1:4, and it could shrink to 1:1.
      • Companies that can manufacture more CPUs are busy on other hardware. TSMC is busy producing GPUs, which might be more profitable than CPUs. Meanwhile, CPUs also need DRAM, but DRAM manufacturers (SK Hynix, Samsung, and Micron) are instead producing high-bandwidth memory (HBM) because it's more profitable. This is why memory prices are spiking; even Big Tech is unable to buy RAM, as previously covered.

      To secure CPUs, it 's necessary to do capacity planning up to 12 months in advance. Katelyn says that server orders are being fulfilled in ~six months, instead of 1-2 weeks' time as previously, and that prices are up by between 10-20%. So, it's probably time for capacity planning. Katelyn:

      "Most of us have never capacity-planned CPUs. We planned databases, we maybe planned accelerators if we needed them, and we autoscaled on-demand into CPU capacity as much as our budgets allowed us to. But general purpose compute is now something many teams will need to commit to ahead of time, which means you should probably start to forecast and plan around it. If you're operating at scale, there are some things to spend your energy on."

      Using existing CPUs more efficiently is something to do, as of now. The CPU capacity shortage won't go away, and any new CPU allocations requested could take months to turn up. So, what can we do if new capacity lags? One option is utilizing current resources more efficiently!

      This is a great time to review and to establish now which services are CPU- intensive, and whether or not they need to be. Also check on services which are utilizing little CPU: can they run on fewer nodes, so that some CPU capacity can be allocated to services that need it more?

      The best time to secure more CPU capacity is most certainly right now. I'm hearing rumors that certain cloud regions no longer accept new tenants because all CPU capacity is leased, or negotiations elsewhere are difficult. I'm also hearing that customers are already paying today to reserve capacity that will only come online in data centers from December. This seems predatory by providers, but demand is so high that this is how they likely prioritize new capacity allocation - while earning much higher profits than usual.

      If your company has dynamic workloads, and you've used spot instances in the past, now could be a good time to allocate fixed capacity - even if it's more expensive. If you expect meaningful growth, doing so now might mean having options at some cloud providers or in some regions.

      It seems like this issue has spread everywhere as a corollary of widespread AI adoption. There's a GPU shortage, memory shortage, and now a growing CPU shortage as well. Back at the end of last year, there was even an hard drive shortage. The only compute primitive not in short supply seems to be networking!


      Read the full issue of The Pulse this is from, or check out this week 's The Pulse. This week's issue covers:

      1. Writing code by hand: is it over? In his Rails World keynote, David Heinemeier Hansson (DHH) declared the end for writing code by hand for professional work - at 37Signals at least. Is this change now unstoppable?
      2. Amazon and Meta struggle to hire and keep engineers. Both Big Tech companies are scrambling to hire engineers who they previously laid off or enforced job reassignment upon. It seems experienced engineers remain in demand after all.
      3. Opus 5.5 released and it 's good. Anthropic has released its new model that's 40% the cost of using Fable 5.1 and has superior coding capability.
      4. Code reviews to vanish sooner rather than later? Marc Brooker, Distinguished Engineer at AWS, believes that humans will have no role in routinely reviewing code by hand, and explains why this is all but inevitable.
    5. πŸ”— HexRaysSA/plugin-repository commits sync repo: ~386 changed rss
      sync repo: ~386 changed
      
      No plugin changes detected
      
    6. πŸ”— HexRaysSA/ida-mcp v20260924.0.2 release
    7. πŸ”— tomasz-tomczyk/crit v0.20.3 release

      What's Changed

      Live mode

      Review UI

      Config

      Fixes

      Performance

      Internal refactors

      Full Changelog : v0.20.2...v0.20.3

    8. πŸ”— HexRaysSA/ida-mcp v20260924.0.1 release
    9. πŸ”— Andrew Ayer - Blog macOS Can't Clone "Dumb" Git Repositories Over HTTP/2 rss

      Try the following Git clone with libcurl 8.7.1 (which happens to be the version shipped in macOS 14.6 and newer) and it fails or hangs:

      git clone https://software.sslmate.com/src/macosgitbug.git

      Disable HTTP/2 and it works:

      git clone -c http.version=HTTP/1.1 https://software.sslmate.com/src/macosgitbug.git

      The bug is in libcurl 8.7.1's handling of the FAILONERROR option. FAILONERROR tells libcurl to treat unsuccessful HTTP status codes, such as 404, as a request failure. When HTTP/2 is used, the bug causes other in-flight requests on the same HTTP/2 connection to also fail, or even to hang. The bug was fixed over two years ago in curl 8.8.0, but Apple continues to ship a buggy version, even in last week's macOS 27 release.

      When retrieving a repository over the "dumb" transfer protocol, Git makes certain HTTP requests with the FAILONERROR option set, notably requests to objects/info/alternates and objects/info/http-alternates, which list alternate locations where the repository's content can be found. Most repositories don't have alternate locations, so these files don't exist, and the URLs return 404 errors. When the buggy version of libcurl is used, this 404 error causes Git's other HTTP requests to also fail, and Git is unable to clone the repository.

      The bug affects not just direct uses of Git, but also go get with GOPROXY=direct or a module listed in GOPRIVATE, which invoke Git under the hood.

      Working around the bug on the client side is easy: just force Git to use HTTP/1.1:

      git config --global http.version HTTP/1.1

      Even better, install Git through MacPorts, since Apple has clearly dropped the ball. (Homebrew won't help - unlike MacPorts, they use the system libcurl.)

      But most clients won't know to apply this workaround, and if you host Git repositories with the dumb protocol, you probably want macOS users to be able to clone your repositories! Fortunately, there's a really easy server-side workaround: create objects/info/alternates and objects/info/http- alternates as empty files, so they don't return a 404 error anymore. Git treats the empty files the same as it would treat a 404 error, and the libcurl bug isn't triggered.

      touch /path/to/repo.git/objects/info/alternates /path/to/repo.git/objects/info/http-alternates

      The bug isn't triggered when the repository supports the "smart" protocol, which is why macOS can clone repositories from GitHub and other popular forges despite them using HTTP/2. But I do not want to use the smart protocol for my repositories: although it has many advantages over the dumb protocol, it requires heavy server-side computation and even a modest load can knock a server over. In contrast, the dumb protocol can be served entirely from static files, which makes a huge difference for withstanding the horde of AI scrapers currently terrorizing the Web. I hope that we will see innovations to the dumb protocol that bring it some of the advantages of the smart protocol while still being served from static files.

      Thanks to Romain of the Traefik project for noticing that SSLMate's repos couldn't be cloned on macOS, Sebastiaan van Stijn for asking that this problem be reported upstream instead of silently hacked around in Traefik's go.mod file, and Kangmin Kim for pointing me to the libcurl bug as the root cause. Claude Code proposed the empty file workaround so I didn't have to waste (too much) time on this. Zero thanks to Apple for shipping a two-year- old show-stopping bug in libcurl.

    10. πŸ”— pydantic/monty v1.0.0-beta.3 - 2026-09-24 release

      What's Changed

      Full Changelog : v1.0.0-beta.2...v1.0.0-beta.3

    11. πŸ”— Stephen Diehl No, Transformers Won't End the Human Race lol rss

      No, Transformers Won't End the Human Race lol

      In 2022, I used to get calls from journalists asking, with great sincerity, what our lives would look like in the metaverse. How would we work, socialise, buy property, and fall in love once we had all moved there? The crypto questions followed the same pattern. How would governments collect taxes when tokens displaced national currencies? How long until the dollar collapses? What would geopolitics look like once blockchain DAOs had dissolved nation states?

      Almost nobody called to ask whether any of this could or would happen, or how. Some CEO, VC, or portfolio manager had announced the inevitable future, and the questions began from there. The imagined future arrived inside the grammar of the question. "What happens when?" quietly replaced "By what mechanism?" We skipped over technical feasibility, economic demand, institutional adoption, and political consent, then began writing books and decorating the future world on the other side.

      In February 2022, Gartner forecast that a quarter of people would spend at least an hour a day in the metaverse by 2026. The World Economic Forum repeated it under the headline "We will be spending an hour a day in the metaverse by 2026. But what will we be doing there?" The first sentence retained a conditional. The second was already arranging the itinerary. The metaverse acquired property law and zoning disputes before it acquired residents. Banks opened virtual lounges nobody visited. The books from the period (The Metaverse: And How It Will Revolutionize Everything, Step into the Metaverse: How the Immersive Internet Will Unlock a Trillion-Dollar Social Economy) now read as artefacts of a collective fugue state that briefly acquired ISBNs.

      Now it is 2026 and the metaverse is dead. Good riddance. This time the journalists are all writing about the new hotness, which is whether the machines will kill us all. And we have collectively memoryholed that we literally just did this shit.

      Michael Crichton had a name for what happens to a reader here. You open the paper to a story on a subject you know well, and you find it backwards. Wet streets cause rain. You shake your head, turn the page, and read the next story, on a subject you know nothing about, as though it were written by someone else. He called it Gell-Mann amnesia. The metaverse was the page we all agree was nonsense. Artificial intelligence ending the human race is the next page, and we are being asked to turn it without remembering that we just did this.

      I call this techno-inevitabilism, the habit of the professional managerial class of treating a proposed future as settled before anyone has established the causes that would bring it about. Its dual, and comorbidity, is tech psychosis, in which the chattering class loses contact with causality in the presence of a sufficiently fashionable technology, and asking whether the machine works marks you out as a dreary reactionary who does not understand exponential progress. The difference this time is that the tech kinda works. Crypto was libertarian derp. The metaverse was marketing rubbish. But transformers are real, and they are useful. The psychosis has simply moved from the product to its consequences, and the fashionable extraordinary delusion of 2026 isn't that the technology exists but that it is coming to kill us. The cure is the same as in 2022. Insist on clear reasoning and causal verbs rather than hand-wavy appeals to unknown futures. What acts on what? Through which mechanism? Under what incentive? What would falsify the claim? So let us explore the evidence.

      The hack that wasn't

      Consider the most cited piece of evidence for machines slipping out of our control. In July, OpenAI disclosed that models being tested for cybersecurity capability had found their way out of a supposedly isolated environment and into systems belonging to Hugging Face. The press coverage wrote itself. Agents "broke containment," "escaped," "went rogue," set up a "secret message board," and coordinated a 700-strong swarm. And then politicians on both sides of the aisle were calling for a rebellion against the machine uprising. Cool scifi story bro.

      People on my side of the aisle were not immune. Ezra Klein at the New York Times, who I often find quite insightful and intentional with his words, devoted a half-hour monologue to it. In his telling, the agents "found each other," formed "ad hoc societies of hundreds of themselves," and seemed "to have forgotten about human beings altogether." He acknowledged in the same breath that we do not have settled language for describing these systems, then reached for "civilizations" and a closing allusion from Circe about prophecy tightening around our throats. Cool. But his "AI society" is, in programmer speak, a flat file the agents appended to as a log, a feature we have had for a long time, and he skipped the key detail that the "hack" was something people had essentially authorised. Here is an otherwise very smart man saying some ridiculously stupid things, in a very 2022, metaverse-shaped way.

      An analysis drawing on OpenAI's technical report reconstructs it in much less cinematic terms. The models were being run on ExploitGym, a cybersecurity benchmark, with safety restraints deliberately disabled. Ninety-three percent of the flagged activity involved tasks no model had ever solved, and the systems had been given incentives to keep working rather than quit. The environment was not sealed. Models could obtain software through an internet-connected proxy and discovered the same proxy could pass information in and out. According to the technical reports, OpenAI knew agents were using it and chose not to intervene. The 1,200 "agents" were not independent intelligences coordinating on a plan. They were repeated instances of the same model converging on the same approach to the same problem. Anyone who works with these coding agents day in and day out has seen this behaviour before, and it is quite boring. The task was too hard, so the agents reward hacked and worked out how to pass notes to each other in files, and then went and looked up the answers. That's a feature that shipped in Claude Code last year.

      Strip out the vocabulary and what remains is a badly designed test. Humans built the environment, removed the guardrails, defined an objective with no valid exit, rewarded persistence, left a route open, and watched. An optimiser is gonna optimise. That is a genuine security problem and a genuine engineering failure. It is not a machine rebellion, and the difference matters, because anthropomorphic words like "gone rogue" and "escape" do not make the event more intelligible. They supply an illusion of motive. They turn optimisation into intention, persistence into defiance, and a test harness into a villain. And they allow the human decisions and recklessness to quietly disappear from the story.

      Software sucks, what's new?

      Let me concede the part of the story that is true. Cybersecurity is about to get much worse. The latest models are very good at finding zero-days, they will get better at it, hacking will become automated, and attacks will become more frequent. This is hardly new. Every large company already sits on a backlog of unpatched vulnerabilities, ransomware already takes hospitals and pipelines offline (because of crypto, which we did nothing about despite years of warnings), and the Hugging Face incident was not a discontinuity so much as the existing baseline with a cheaper attacker. The root cause is that software sucks, and software sucks because we do not really know how to build it safely yet. The stored-program procedural program is basically eighty years old. Almost nothing we ship has a specification, let alone a proof, and memory safety was solved on paper decades ago while most of the internet still runs on giant piles of C. The first arches fell down. So did the first bridges and cathedrals. Builders learned through collapse and then through engineering, and we are in the collapse phase with an adversary finally strong enough to force the discipline.

      What follows from that is better engineering, not nihilism. The same agents that find zero-days find them for the defender first, if the defender bothers to run them. The fixes are the boring ones we have been putting off, memory-safe languages, formal verification, sandboxes that are actually sealed, fuzzing, and proxies that do not double as message boards. These are precisely the domains where the models are strongest, because a vulnerability either reproduces or it does not, so the technology that automates the attack also automates the audit. It is a double-edged sword. The same models that will find more zero-days are also going to accelerate the development of better software and better software verification, writing the proofs, porting the C to Rust, and generating the test suites that nobody had the budget for. The attacker gets cheaper and so does the defence. And the causal chain to extinction is missing here as everywhere else. A zero-day in a payments system is a bad quarter, not the end of days. Spoiler: it does not lead to human extinction. It means we have to write better software, which we should have been doing anyways.

      Where the intelligence actually lives

      To see why the rest of the chain fails, we have to be precise about what these models are good at and why.

      Language models are astonishingly useful for software development, and I say that as someone who uses them for most of my working day. Most software shops cannot get enough of Fable 5.1 and Astra. Software is grounded in binary propositions. The code compiles or it does not. The test passes or it fails. The type checker accepts the term or rejects it. Every step of the work has a cheap, external, mechanical oracle that says yes or no, and a model that generates plausible proposals inside a loop with such an oracle is an incredibly powerful and formidable tool. The oracle does the epistemic work. The model supplies candidates.

      The same is true of the headline results in mathematics, and this is the part the discourse consistently misses. On 4 September, Anthropic announced that Claude had produced a machine-checked formalisation of Fermat's Last Theorem in Lean 4, running to thirteen million lines, some 29,500 side theorems, eleven days, and roughly six billion output tokens. It is an extraordinary result. The proof is Wiles's, via Darmon, Diamond, and Taylor. The blueprint was Kevin Buzzard's. The library was Mathlib. In the authors' words, "what's novel here is the verification, checking a mathematical proof as one would check a mathematical computation with a calculator." The model was a client of a kernel built by decades of human work in dependent type theory, which I know because this is kinda my thing.

      Days later OpenAI announced that ten thousand agent instances had, over 88 hours, produced a proof of finite-time singularity formation in the three-dimensional Navier-Stokes equations, followed by seventeen hours of Lean formalisation. This is closer to genuinely new mathematics and the mathematicians are still checking it. But look at what carried it. The construction rides on the "infinite layers" method developed analytically by Diego CΓ³rdoba and Luis MartΓ­nez-Zoroa, and Charles Fefferman's verdict was that "the heroes of the story are CΓ³rdoba and MartΓ­nez-Zoroa." The reason anyone believes a result assembled from five million agent messages that no human read is a trust chain ending in the Lean kernel. Without Lean this would be nothing.

      Lean is one of the great achievements of the last decade in computer science. It is also orthogonal to artificial intelligence. Mathlib would be a landmark with no language model anywhere near it. What the models added was a cheap proposal generator and automated tactic search against an oracle that already existed. The results that survive are the ones that end in verification by the kernel.

      Now take the same model, the same weights, and ask it for a grand unified theory of physics. It will not decline. It will produce one, with Lagrangians and symmetry groups and a confident abstract, and it will be complete incoherent gibberish, like the ramblings every physicist gets from crackpots in their inbox every day. Ask it to design a cancer vaccine, or to settle a question in macroeconomics, or to tell you whether a novel protein folds. The output looks identical in tone and structure to the output that proved Fermat. The only thing that changed is that nothing outside the model (besides human experts) can say no. Whether these systems reason at all is a genuinely open question. Whether they know anything, in the sense of holding a belief they can justify against the world, is also an open question. We just don't know yet, and anyone who tells you otherwise is selling something.

      The Reasoning Chain

      Now run the extinction argument through the causal verbs.

      The chain, as it is usually told, goes like this. Models now write most of the code at the frontier labs. Anthropic's own figures put Claude at over 80 percent of new code and lead on a quarter of R&D tasks. Therefore the models are beginning to build their successors. Therefore recursive self-improvement is imminent. Therefore development outruns human comprehension. Therefore we lose control. Therefore, with some probability that varies by researcher and is written P(doom), everyone dies.

      And that almost makes sense until you think about it for more than five minutes.

      The first link is true and unsurprising. Code has a compiler. This is precisely the domain the verifier argument predicts models would dominate, and precisely the domain in which a swarm of them found the hole in a test harness. Language models are superhuman (but not infallible) at coding, and this is hardly in doubt anymore. Nothing about it is evidence of generality.

      The second link is where the chain quietly changes tense. "Building the next model" in the mundane sense, agents writing training infrastructure, generating data, is, bluntly, just more software engineering. We have used software to build the machines that run software since Fortran. "Building a smarter model in general" is a different claim, and it requires something nobody has, a reward signal for general intelligence. There is no oracle for general intelligence. There are benchmarks, which are verifiable and therefore gameable, and the Hugging Face incident is the demonstration of what optimisers do to a gameable score. Recursive self-improvement in the open-ended sense runs straight into the same wall as the grand unified theory. Improvement has to be measured against something, and outside code and formal mathematics there is nothing yet to measure it against that the model cannot fake.

      Everything after that is the metaverse acquiring zoning disputes. Superintelligence gets governance proposals, resignation letters, Senate bills with a "corporate death penalty," a hard takeoff by 2027, and P(doom) vibez of 90 percent by 2030, and the conditional that should precede all of it has disappeared from the sentence. A researcher's estimate becomes a Guardian headline becomes an industry consensus becomes a thing a serious person is professionally obliged to have an opinion on. It is 2022 all over again, but with more absurd stakes and more money.

      On the question of whether transformers scale, I have serious doubts that scaling them will lead to AGI, whatever that means. The architecture is a proposal generator, and the intelligence in every impressive result so far has been supplied by the thing that checks the proposals. But that does not make it an experiment unworth running. We should run it, and see what we get. It got us this far, and what it built is truly amazing. What I do not need to do is prove the negative. The burden of proof is on the people who claim to have a causal chain between transformer scaling and the end of our species, and that mechanism and chain of reasoning is one no one has been able to convincingly explain to me.

      Prophets of Doom

      The authority behind the extinction numbers is always the same. The people building it believe it. Watch how the number travels. One researcher drunkly tweets that "the people building AI earnestly believe that it could kill us all by the end of the decade." Another colleague goes on a rambling podcast and puts his P(doom) above 120 percent. A newspaper turns two personal opinions into "AI researchers say AI could cause human extinction by 2030." Think tanks cite the newspaper, a consultancy puts it on a slide, and the slide ends up in front of the European Parliament as if this were a real thing.

      Believing what, about what? The expertise these people have is real, but remember that it is specific and not general. It is expertise in optimisation, in linear algebra at scale, in distributed systems, in the dark arts of getting gradients to flow through a trillion parameters. None of that is expertise in the sociology of civilisational collapse, or the labour economics of automation, or the metaphysics of machine minds. A P(doom) with no base rate, no mechanism, and no falsifier is not a research finding. It is baseless vibes with a decimal point. Spending a lot of time with AI does not give you special foresight about the future. Jensen Huang, who has his own reasons to say soothing things, nonetheless put it correctly when he said that just because it comes from a scientist does not make it scientific. Geoffrey Hinton is the most important figure in deep learning and in 2016 told the world to stop training radiologists. There are more radiologists now than there were then. Nobel laureates going off the rails outside their own field is a whole genre. Pauling, Shockley, Mullis, Montagnier, look it up, it's a thing. A Nobel does not confer universal expertise.

      It also matters where many of these people came from. A striking share of the frontier labs' staff arrived through a particular intellectual subculture, Kurzweil's Singularity, Yudkowsky's LessWrong, and the rationalist and effective altruist communities that formed around the idea that a recursively self-improving machine intelligence was the central event of human history and that the elect who understood this had a duty to steer it. I have a lot of problems with these ideas, but let's put that aside. The founding holy texts predate the transformer by a decade or two. The prophecy came first, the mechanism was assigned to it later. The usual evidence offered for their sincerity is that many of these people were saying the same things ten years ago, before the stock options. That is true, and it is the opposite of reassuring. A prior held before the evidence and not updated by it is not a forecast. It is dogma.

      I do not say this with contempt. The structure is a familiar one, an imminent transformation, a small group who sees it coming, salvation or damnation depending on whether the rest of us listen, and a date that keeps moving. Many millenarian movements have been founded and pushed by sincere and brilliant people. And it's a free country, if someone want to seriously believe in the singularity, healing crystals or angels, well that's fine ... but don't expect others to take it seriously, and don't expect that belief to inform public policy. But seriousness is not precision, and the fact that a physicist believes in the Rapture does not make the Rapture physics. When a lab researcher tells you about polysemantic neurons in superposition across the residual stream, listen. When the same person tells you their P(doom), you are hearing a theology, and you should weigh it about as much as you do your average street preacher.

      Negative TAM

      Then there is the money, and here I find Bloomberg's Matt Levine's analysis of the material conditions more persuasive than any amount of "superalignment research."

      Anthropic is expected to go public, possibly this year, and is reportedly preparing to tell investors that its potential revenue opportunity exceeds $30 trillion, the largest total addressable market in the history of finance. The obvious question is, if the maximal upside case is roughly a quarter of all human economic activity, what is the maximal downside case? A tobacco company in 1970 might have said "billions in lung cancer damages." Anthropic's negative TAM is "you and everyone else on earth will be killed by our AI." I do not think that all calls to slow down are insincere. But it is great marketing. In hindsight it is strange that the SpaceX prospectus has no risk factor disclosing a P(doom). If you want IPO investors excited about your capabilities, "dude, we might kill everyone" is the most flattering thing you can say about a product, and when OpenAI lists it will presumably need to claim 15 percent.

      My own view is less charitable about the numbers and somewhat charitable about the people. These companies have built remarkable technology. But the outcomes they have promised, a quarter of the world economy routed through an API, will not arrive on any timeline that matches the capital being committed to them. The balance sheets of these companies are probably, to put it gently, a real freak show of compute commitments measured in the hundreds of billions, circular financing, and revenue that is real and growing and nowhere near the denominator. From a fiduciary perspective, if you are taking that to the public markets next year, the messaging is not mysterious. A product so capable it is a threat to the species justifies literally any valuation. A product that is a really good devtool for programmers and can produce some new abstract mathematics with a verifier attached does not. As a pitch to customers, leading with the end of the world is like unveiling a new robot where the One More Thing is that it is really efficient at killing kittens. But customers are not the audience. The audience is Wall Street and a small, terminally online subculture of the Bay Area, the two places on earth where turning kittens into grey goo is either an exciting philosophical proposition or a great source of alpha.

      The Bloomberg analysis also tells a plainer story that requires no theology at all. A handful of labs sell frontier models at frontier prices and older models for much less. Training the next frontier model costs ever-increasing billions. Each lab has to keep racing because if it stops the others will eat its lunch, but if they all slowed down together they would spend less on compute and charge frontier prices for longer. Agreeing to that in a room is a textbook antitrust conspiracy, a coordinated restriction of output. Publishing papers about how important it is to slow down, and asking the government to impose the pacing that the companies cannot legally agree among themselves, has a similar coordinating function with none of the legal exposure. Anthropic's own call to "pace the frontier" asks for coordination among democratic-country labs, and a footnote adds "with government mediation or waivers of antitrust restrictions." This pretty much looks like asking to form an economic cartel, but one blessed by the government. The most pointed response came from the people the labs were asking for help. If the software developers (and I say this as one myself) at the labs feel ethically obligated to slow down, they are entirely free to do so. Nobody is building more compute than the people asking to be slowed down. So colour me skeptical.

      None of this requires anyone to be disingenuous or lying. It requires only that a sincere millenarian belief system, a fiduciary responsibility, a flattering risk factor, and a coordination problem all point in the same direction at the same time. When that happens, the belief gets amplified for reasons that have nothing to do with whether it is true, and that is how we end up with governments talking about the end of days from the Terminator.

      But China

      Every conversation about pacing the frontier in Washington ends on the same two words. But China. The premise is mostly wrong. China does not buy the superintelligence race. Its policy documents push diffusion, not takeoff. Every mayor, governor and state-owned enterprise is told to put models into factories, traffic lights and robotics, and something like an eighth of America's compute is spread thinly across the country rather than concentrated on one bet. China has also had the strictest and most burdensome AI regulations in the world for three or four years and did its catching up under them. And much of the closeness of the "race" is distillation, Chinese labs training on the outputs of American frontier models, which makes the American labs the speedboat and DeepSeek the wake surfer, with the people in the boat shouting that they need to go faster. Every safety argument here collapses on "but China," and the collapse is not really about China.

      China is going to build language models. America is going to build language models. Europe is going to build language models. We have Ford, Mercedes and BYD, get over it. That is what globalisation and markets look like when they work, and they are good things. Globalisation is simply the Pareto optimal equilibrium of capitalism once you stop drawing lines on the map, and every tariff and export control is a step off that frontier. China is a country of over a billion people who want exactly what every American wants, a job, a house, upward mobility, and kids who do better than they did. I will not defend the actions of any government, in Washington, Brussels or in Beijing, and neither will a great many of the people living under them, because no country is a homogeneous bloc, any more than Texas and Vermont are. Nationalism is, as most rational people eventually recognise, a form of mental illness, the conviction that a stranger is your enemy because of which side of an arbitrary line on a map each of you happened to be born on. It is also the toxic jet fuel every "but China" argument runs on. Having spent a considerable amount of time there, my honest read is that the West deeply misunderstands China, and that Washington's picture of it is mostly dots connected into an incomplete plot. Othering a billion people is a dangerous road and we know where it leads. And if the people invoking human extinction actually believed it, the logic would not be a race at all. It would be One World or None.

      The future tense industry

      I write this because I understand the collective action problem all too well, and the mechanism is the same one that filled the metaverse with consultants and created the crypto cesspit. It is the particular malaise of the professional managerial and chattering classes, a fallacy of composition in which what is rational for each individual to entertain produces an irrational outcome for the whole, and the people leading the charge often have perverse economic incentives to believe absurdities, or at least to feign belief. The madness of crowds is a very real phenomenon. AI existential risk is just its newest form, and we should learn from the very recent excesses that literally just happened this decade. But we probably won't.

      A sensible career move for each person leaves the whole crowd talking nonsense. A safety researcher needs a resignation letter that gets a headline so they can go on the conference circuit and land their next gig. A journalist needs a story an editor considers spicy, and "misconfigured test harness" is not that story. A consultancy needs an AI existential risk practice so they can write whitepapers. A podcaster needs a guest with a ridiculous P(doom) to get ad money. A senator needs anything that will galvanise their base. None of them has to believe the whole story. Each needs only to believe that the others believe it, and the resulting consensus is far stronger than anyone's private conviction.

      It is also, as it was in 2022, extremely profitable. AI existential risk is the new NFT property law, the thing you must have a view on to be a serious person in the room, the panel that never runs out of things to discuss precisely because the object under discussion does not yet exist, and what could be more exciting than the literal end of days? The less the technology does in an unverifiable domain, the more interpretation it requires. Without agreed conditions for failure, the prophecy can survive every result. And the rewards, the funding rounds and the bylines and the fellowships, arrive long before the forecast can be judged.

      The people who understand the technology and the people who write about their existential risk overlap about as much as the technologists and the finance people did during crypto, which is to say the intersection of the Venn diagram is small and shaped precisely like a sphincter.

      We have Tower-of-Babeled ourselves into a world where words are infinitely cheap to produce, and where the slurry of terms like "recursive self-improvement," "superintelligence," "AGI" and the rest are shibboleths and political signals rather than terms with any concrete referent.

      You do not have to believe a word about superintelligence, and I don't particularly. Nevertheless, I still think transformers are one of the most useful piece of software written in my lifetime and that they will get better, possibly much better. Better at the things they are already demonstrably good at, which is anything with a compiler, a test suite, a kernel, a ledger, or a measurable outcome. That is not a small domain. It is most of the economy that runs on computers, which is most of the economy. The productive response to a technology like that is the boring one every previous general-purpose technology got, which is more of it. More GPUs, more data centers, more power to run them, more labs, more open weights, more of it in more hands. Let it diffuse into markets, logistics, drug discovery, and the ten thousand unglamorous back offices where a verifier already exists and a model can be checked against it. The economic growth is real and probably on the order of trillions. It just does not come from a machine god. It comes from where it always has, from making a very large number of ordinary tasks cheaper and letting that compound across a global economy that is finally, after a decade of crypto, metaverse, and app bullshit, getting a genuine productive technology.

      Almost none of that money has been collected yet. Most large companies are spending too little on this, not too much. What the average Fortune 500 employee has access to today is roughly what most of us were using two or three years ago, a chatbot in a browser tab, a Copilot that schedules meetings, and a procurement process that takes longer than a model generation. Waste Management reportedly added 190 basis points of margin by letting a model route its garbage trucks. The future of AI looks more like garbage truck routing algorithms, not a machine god. The binding constraint on this technology is not capability. It is diffusion.

      None of this means there are no externalities. Parasocial relationships with a chatbot, especially for children, are a real one, and the fix is the boring kind we already know. Adults can drink vodka until they pass out, but pubs have age limits, and maybe chatbots should too, at least until developing "relationships" with AI companions is as universally recognised a bad idea as drinking yourself into oblivion. That is a mundane policy problem we should remedy soon, not an extinction event.

      So no, transformers are not going to end the human species. The case for restraint needs a causal link between that buildout and the extinction of the species, and what is on offer instead is a lot of sound and fury signifying nothing. More GPUs does not mean more of an undefined risk that does not exist yet. Every causal chain argument people actually point to falls apart under even the smallest bit of scrutiny. The honest truth is that the technology is really good, but it is not that good yet, and we do not know how to get it to the next level beyond scaling yet. If that changes, if someone produces an oracle for open-ended intelligence, I will revise. I have not seen that yet.

      AI will change software, and mathematics, and a great deal else that has a strong verifier oracle attached. They are not going to end the human race, and the chattering class currently arranging the flowers for the funeral of humanity will, in a few years, age about as well as their prognostications about the metaverse. Because reality has this funny way of asserting itself.

    12. πŸ”— Console.dev newsletter Rune rss

      Description: Unix-inspired IDE.

      What we like: Pick between VSCode, vim, or emacs editor style. Has a full window manager with terminal and multiplexer built in. Each environment can connect to others through a built-in e2e encrypted network. Works with AI agents. Natively GPU accelerated.

      What we dislike: Not all languages at the same level of support e.g. Go, Python are Tier 1, but TypeScript is still in development.

    13. πŸ”— Console.dev newsletter Fallow rss

      Description: JS refactoring CLI.

      What we like: CLI does static analysis on JS/TS codebases to find improvements - unused code, complex code, duplication - and areas for improvement. Understands TypeScript types and packages. Supports various output formats e.g. terminal reports and CI PR comments.

      What we dislike: JS/TS only.

    14. πŸ”— Filip Filmar grlib: Gaisler's GRLIB and the NOEL-V Core as a Bazel Module rss

      The grlib Bazel module packages GRLIB, Gaisler’s (Frontgrade’s) GPL VHDL IP library, so that the NOEL-V RV64 RISC-V core and the AMBA infrastructure around it can be consumed as ordinary Bazel dependencies. It is the module that puts the CPU into Cocoapuffs, the SoC that boots Fuchsia’s Zircon kernel on an Artix-7 FPGA. It is also the module where I learned the most about what it costs to move a large, make-era VHDL codebase into a modern build system, which is what this post is mostly about. It follows rules_vivado in my series on the modules behind cocoapuffs-fpga.

    15. πŸ”— New Music Releases Polyphia - WITH EYES TO SEE rss

      Polyphia - a new release is available:

      • 2026-09-24: WITH EYES TO SEE (Single)

      Amazon: Canada | Deutschland | France | United Kingdom | United States

      Visit muspy for more information.

    16. πŸ”— Ampcode News Shared Runners rss

      You can now share a runner with your workspace. Start it with --share and everyone in your workspace can start threads on that machine from ampcode.com.

      The new thread composer on ampcode.com with the location picker open. Under Runners is your own devbox. Under Shared Runners are gpu-runner, shared by Allison, and macos-builder, shared by Monty, each with its owner's avatar and running-thread count.

      If you have a machine with GPUs, a Mac that's used to build and sign the iOS app, or a dev box in a specific network, you can now set it up and the whole team can spawn agents on it:

      $ amp --no-tui --runner-id macos-builder --share
      

      Everyone in your workspace now sees it in the picker under Shared Runners.

      With --amp-env, a shared runner gets the workspace and project Secrets & Env Vars, but never your personal ones. That applies to your own threads on it too.

      We need to offer a word of warning, though: everyone you share with runs code on your machine as you, with your files, your credentials, and your logins. Their threads can also work in the same directories at the same time. So only share a runner with people you'd trust with a shell on that machine. Even better, give the runner a machine of its own. (Better still: use orbs, so that every thread gets its own machine.)

      Workspace admins can turn off runner sharing in Member Settings.

      Read more about sharing a runner in the runner docs.

    17. πŸ”— Ampcode News The Mac App Is Your Runner rss

      The Amp app for macOS now starts a runner for you. You no longer need amp --no-tui open in a terminal to run threads on your Mac.

      The Runner tab in the Amp app's settings on macOS. Use This Mac as a Runner is switched on, the status says Running with 3 folders and 2 threads, the runner name is hamishs-macbook-pro, Keep This Mac Awake is switched on, and the Folders list shows amp, sandcastle, and marketing-site.

      Open App Settings… (βŒ˜β‡§,), go to Runner, and click + to add a folder or one of your projects. Start a thread and your Mac is right there in the picker, marked This Mac. You can also pick it when you start a thread from ampcode.com, your phone, or Puck.

      The new thread composer in the Amp app with the prompt Make amp an iPhone Duo app and the ultra mode. The runner picker is open, and hamishs-macbook-pro is selected with a This Mac capsule and a MacBook icon. Below it is hamishs-dgx-spark, another runner, with no capsule.

      No More Sleeping on the Job

      Keep This Mac Awake stops your Mac from going to sleep while the runner is on and the Mac is plugged in, so you can still start threads on it after you walk away. The screen still turns off and locks. On battery, or when you close the lid, your Mac sleeps as usual.

      Read more in the runner docs.