<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4" -->

---
title: The Case of the Vanishing Fiber: A Ruby Mystery | daily.dev
description: A Sherlock Holmes-style narrative investigates an intermittent Ruby crash traced to a use-after-free bug in CRuby's Fiber implementation. The story walks...
canonical: https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: The Case of the Vanishing Fiber: A Ruby Mystery | daily.dev
og:description: A Sherlock Holmes-style narrative investigates an intermittent Ruby crash traced to a use-after-free bug in CRuby's Fiber implementation. The story walks...
og:url: https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4
og:image: https://api.daily.dev/og/posts/DGQPxvNp4.png
og:image:alt: The Case of the Vanishing Fiber: A Ruby Mystery
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The Case of the Vanishing Fiber: A Ruby Mystery

**[RUBYLAND](https://daily.dev/sources/rubyla)** · 7 min read · 1 upvotes · 0 comments

## Summary

A Sherlock Holmes-style narrative investigates an intermittent Ruby crash traced to a use-after-free bug in CRuby's Fiber implementation. The story walks through reproducing the crash with AddressSanitizer, isolating it to a raw C pointer (rb_fiber_t*) that the garbage collector could not see as a live reference, and reducing the HTTP-triggered failure to a minimal pure-Ruby test using nested Fiber#resume, Fiber.yield, and GC.start. The root cause traces back to a prior fix (Bug #21955) that broadened post-switch Fiber handling, making the latent lifetime bug observable. The final fix adds an RB_GC_GUARD after fiber_switch to keep the owning Fiber object alive across the suspending switch, and was backported to Ruby 3.4.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://www.codeotaku.com/journal/2026-08/the-case-of-the-vanishing-fiber/index>

## Questions this post answers

### Why did Ruby 3.4.10 crash intermittently when using Async::HTTP with truncated responses?

The crash was caused by a use-after-free bug in Ruby's Fiber implementation, not by Async::HTTP itself. A C function (fiber_switch) held a raw pointer to a Fiber's internal structure across a suspending switch to another fiber; if that other fiber's execution removed the last reference to the original Fiber and triggered garbage collection, the Fiber object was freed while the paused C frame still held a stale pointer to it, causing a crash when execution resumed.

_Developers chasing intermittent native crashes in Ruby async code can track fixes like this GC lifetime bug on daily.dev._

### What was the root cause regression that made the Ruby Fiber use-after-free bug appear in version 3.4.10?

The bug became observable after the fix for Bug #21955, which correctly broadened a post-switch check that had previously applied only to Fiber#resume so it also covered Fiber.yield and Fiber#transfer. This change made code paths read a Fiber pointer after a suspending switch more often, exposing a pre-existing but rare invalid-lifetime read that reverting the check would only have hidden rather than fixed.

_Teams debugging regressions after a Ruby point release can follow root-cause writeups like this on daily.dev._

### How was the Ruby Fiber use-after-free bug fixed in CRuby?

The fix copies the owning Ruby Fiber object into a variable named fiber_value and places an RB_GC_GUARD call after the fiber_switch call returns. The guard prevents the compiler from treating fiber_value as dead early, keeping the Fiber object visibly alive to the garbage collector across the suspended interval so its underlying rb_fiber_t structure is not freed while still needed. The fix was backported to Ruby 3.4.

_Ruby maintainers guarding against native use-after-free bugs can keep up with GC-lifetime fixes via daily.dev._

## Similar posts on daily.dev

- [When Your Hash Becomes a String: Hunting Ruby’s Million-to-One Memory Bug](https://daily.dev/posts/when-your-hash-becomes-a-string-hunting-ruby-s-million-to-one-memory-bug-nhlgsnql3) · Closer to Code · 0 upvotes · 0 comments
- [The Case of the Missing Line: A Ruby Mystery](https://daily.dev/posts/the-case-of-the-missing-line-a-ruby-mystery-z7t9cbv0f) · RUBYLAND · 0 upvotes · 0 comments
- [How a flaky test exposed a Redis use-after-free](https://daily.dev/posts/how-a-flaky-test-exposed-a-redis-use-after-free-yjzv1yw4v) · Hacker News · 0 upvotes · 0 comments
- [CVE-2026-46727: Use-after-free in pthread-based getaddrinfo timeout handler](https://daily.dev/posts/cve-2026-46727-use-after-free-in-pthread-based-getaddrinfo-timeout-handler-wsx4kaak6) · Ruby · 1 upvotes · 0 comments
- [Making Failure More Predictable in Ruby Systems](https://daily.dev/posts/making-failure-more-predictable-in-ruby-systems-d5v9axxif) · RUBYLAND · 0 upvotes · 0 comments

---

Tags: [#ruby](https://daily.dev/tags/ruby)

[View this post on daily.dev](https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"The Case of the Vanishing Fiber: A Ruby Mystery","url":"https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4"},"datePublished":"2026-08-14T05:52:07.523Z","dateModified":"2026-09-13T21:34:12.045Z","description":"A Sherlock Holmes-style narrative investigates an intermittent Ruby crash traced to a use-after-free bug in CRuby's Fiber implementation. The story walks...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/90abb08704817087cef7d5932050d02f?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/90abb08704817087cef7d5932050d02f?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"RUBYLAND","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"RUBYLAND","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/rubyla","url":"https://daily.dev/sources/rubyla"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"ruby","timeRequired":"PT7M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"RUBYLAND","item":"https://daily.dev/sources/rubyla"},{"@type":"ListItem","position":3,"name":"The Case of the Vanishing Fiber: A Ruby Mystery"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/the-case-of-the-vanishing-fiber-a-ruby-mystery-dgqpxvnp4#faq","mainEntity":[{"@type":"Question","name":"Why did Ruby 3.4.10 crash intermittently when using Async::HTTP with truncated responses?","acceptedAnswer":{"@type":"Answer","text":"The crash was caused by a use-after-free bug in Ruby's Fiber implementation, not by Async::HTTP itself. A C function (fiber_switch) held a raw pointer to a Fiber's internal structure across a suspending switch to another fiber; if that other fiber's execution removed the last reference to the original Fiber and triggered garbage collection, the Fiber object was freed while the paused C frame still held a stale pointer to it, causing a crash when execution resumed. Developers chasing intermittent native crashes in Ruby async code can track fixes like this GC lifetime bug on daily.dev."}},{"@type":"Question","name":"What was the root cause regression that made the Ruby Fiber use-after-free bug appear in version 3.4.10?","acceptedAnswer":{"@type":"Answer","text":"The bug became observable after the fix for Bug #21955, which correctly broadened a post-switch check that had previously applied only to Fiber#resume so it also covered Fiber.yield and Fiber#transfer. This change made code paths read a Fiber pointer after a suspending switch more often, exposing a pre-existing but rare invalid-lifetime read that reverting the check would only have hidden rather than fixed. Teams debugging regressions after a Ruby point release can follow root-cause writeups like this on daily.dev."}},{"@type":"Question","name":"How was the Ruby Fiber use-after-free bug fixed in CRuby?","acceptedAnswer":{"@type":"Answer","text":"The fix copies the owning Ruby Fiber object into a variable named fiber_value and places an RB_GC_GUARD call after the fiber_switch call returns. The guard prevents the compiler from treating fiber_value as dead early, keeping the Fiber object visibly alive to the garbage collector across the suspended interval so its underlying rb_fiber_t structure is not freed while still needed. The fix was backported to Ruby 3.4. Ruby maintainers guarding against native use-after-free bugs can keep up with GC-lifetime fixes via daily.dev."}}]}
```

