<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: christine</title>
    <description>The latest articles on DEV Community by christine (@cseeman).</description>
    <link>https://dev.to/cseeman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F77243%2F5dbbe7a7-3411-4644-b415-cc14b80d7fbc.jpg</url>
      <title>DEV Community: christine</title>
      <link>https://dev.to/cseeman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cseeman"/>
    <language>en</language>
    <item>
      <title>AI for everything besides coding</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Wed, 28 Oct 2026 23:48:46 +0000</pubDate>
      <link>https://dev.to/cseeman/ai-for-everything-besides-coding-4hen</link>
      <guid>https://dev.to/cseeman/ai-for-everything-besides-coding-4hen</guid>
      <description>&lt;p&gt;I use Claude Code all day for my job. That part of my AI use is easy to describe: there's a repo, a ticket, a diff at the end.&lt;/p&gt;

&lt;p&gt;Chats are different. When I open a chat, it's usually because I want to search for something but I already know the search will go badly. The question is a little odd, or very specific to me, or I want the answer from one particular angle. So I wondered: if AI is improving my life anywhere outside of work, it's probably in those chats. Did they save me time I spent on something better, or did it go to mindless browsing (which I am trying to cut down on)?&lt;/p&gt;

&lt;p&gt;I went back through everything from mid-August, around when I updated our family trip plan, to last weekend. That's August 15 to October 25, 2026: 20 conversations.&lt;/p&gt;

&lt;p&gt;The short version: the chats that helped most went past the question I typed, to what I was actually trying to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually asked
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket&lt;/th&gt;
&lt;th&gt;Chats&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trips&lt;/td&gt;
&lt;td&gt;Orlando family trip plan, Orlando packing list, Epic Universe itinerary, Rocky Mountain Ruby hotel comparison, an easy hike in Boulder, finding a cruise travel agent for my mom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health&lt;/td&gt;
&lt;td&gt;A treatment plan and tracker for my eyes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recipe tweaks and odd asks&lt;/td&gt;
&lt;td&gt;Flax and chia crackers, anti-inflammatory swaps for the week's meals plus an Aldi list, a cabbage side for pierogi, the best value on a restaurant wine list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;House and family&lt;/td&gt;
&lt;td&gt;A cat food smell in the kitchen, piano tuition reminders, a reminder for a cat photo contest deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hobbies and volunteering&lt;/td&gt;
&lt;td&gt;Orchestra Omaha concert program and intro speech, a fragrance hunt, copying files on a Steam Deck&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Work, but not code&lt;/td&gt;
&lt;td&gt;Catching up on a week of Slack, summarizing a tech industry lawsuit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Almost none of these would have gone well as a search.&lt;/p&gt;

&lt;h2&gt;
  
  
  I asked about price and got a perfumer
&lt;/h2&gt;

&lt;p&gt;The best result came from a fragrance chat. In September I asked for the best price on Hermès Un Jardin sur le Nil, then the per-ounce cost at FragranceX, then: if I really like this one, what else should I try?&lt;/p&gt;

&lt;p&gt;It answered by the perfumer instead of the brand. Un Jardin sur le Nil is a Jean-Claude Ellena fragrance, and the thing I liked about it is his style: watery, green, light. So it pointed me to six more in that style that FragranceX carries.&lt;/p&gt;

&lt;p&gt;I bought two from FragranceX: Le Jardin de Monsieur Li, 3.3 oz for $66.45, and a 1.7 oz Un Jardin sur le Nil for $71.87. I wouldn't have bought from that site at all without the recommendation and the price breakdown. They're my new go-to fragrances.&lt;/p&gt;

&lt;p&gt;A search for "perfumes like Un Jardin sur le Nil" would have given me a listicle. I got an explanation of why I liked the one I liked, and that's what made the next purchase easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  I asked for an air freshener and got sent to a cabinet
&lt;/h2&gt;

&lt;p&gt;I have three cats, and our open kitchen and living room had a lingering cat food smell. I asked for the best air freshener strategy and told it to ask me as many questions as it needed. After a few rounds, its answer was that an air freshener should be the last step.&lt;/p&gt;

&lt;p&gt;The smell was a constant haze, worse on warm days, with no obvious source. That pattern pointed to something soaking up odor and slowly releasing it. The likely culprit was the cabinet that holds the recycling bin. Rinsed cans still carry fish oil, and it guessed the inside was bare wood that had been absorbing it for months. The second suspect was the gasket on the microchip feeders (bowls that only open for the cat whose chip they read), which I had never taken out. The plan: scrub and seal the cabinet with a shellac primer, take the feeders apart, run the range hood at feeding time, and only then add scent. It also warned that essential oil diffusers aren't safe around cats.&lt;/p&gt;

&lt;p&gt;Then it corrected itself. When I asked whether its air purifier pick was the most recommended, it said no. It had given me the Austin Air HealthMate, an odor specialist that only covers about 234 square feet. For an open floor plan it suggested two quieter Alen BreatheSmart 75i units instead.&lt;/p&gt;

&lt;p&gt;I had never looked inside that cabinet. It turned out to be finished, so the sealing step wasn't needed. I didn't do the whole plan either. I deep cleaned the trash cans and the cabinet and put a box of baking soda in the back. It helped, and I never bought an air freshener.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same thing, on a smaller scale
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eyes.&lt;/strong&gt; I have meibomian gland dysfunction, where the oil glands in your eyelids don't work well, and wanted a routine that balanced heat against inflammation. It built a daily log I keep on my phone: redness, burning, whether I did the heat routine, glasses or contacts. Once it has three days on each side, it compares contact days against glasses days, so I'll have a real number for my eye doctor. When I asked for a nightly reminder, it said a phone alarm would be more reliable than anything it could set up, and to label the alarm with the actual steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boulder.&lt;/strong&gt; For Rocky Mountain Ruby, a Ruby conference in Boulder where I was speaking, I asked for hotels within a 20-minute walk of eTown Hall, under $750. I stayed at its pick, Basecamp Boulder, and so did another speaker. The walk was easy. It also told me to skip Mount Sanitas on day one, since I'd be going from Omaha's elevation to 5,430 feet. When I asked what coffee to bring because hotel coffee is awful, it checked first: Basecamp puts a kettle and pour-over in most rooms. Heads up for coffee snobs: the in-room pour-over is not good. Skip it and go out for coffee in Boulder instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orlando.&lt;/strong&gt; The night before Epic Universe I asked for early entry times. It gave me 9am early admission and 10am open, and said the times came from TouringPlans because Universal's own hours page wouldn't load. That's the answer to "can I trust this," which I hadn't asked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack.&lt;/strong&gt; The Monday after Orlando, I asked how to catch up on a week of Slack. It worked out the approach with me before running anything, so I got a to-do list instead of a channel-by-channel summary. In 23 minutes I had the two threads where I still owed an answer, and draft replies for both.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When it just answered the question
&lt;/h2&gt;

&lt;p&gt;The rest of the chats did exactly what I asked, and that was fine. The cracker chat took five minutes: more flax and chia, crackers because I had Parmesan, and measurements fixed after it called for 0.8 cup of something. The anti-inflammatory dinner swaps ended with Barilla Protein+ because I don't like whole wheat pasta, and an Aldi list in my email. The piano studio's tuition policy became a year of calendar reminders with each month's amount. Orchestra Omaha, where I play viola and help out on the board, got a concert program and a 90-second intro with a Huskers joke. At Talus, a restaurant in Omaha, it picked the best buy on the wine list, Louis Jadot Mâcon-Villages at $9 a glass. I ordered it, a nice light white, and it was better than the cheese board it was supposed to pair with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I lead with my constraints.&lt;/strong&gt; "I do not wear shorts." "I have Parmesan." "3 cats." "I'm not on my laptop that late." That's what gives it enough to notice when I'm asking the wrong question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I edit replies like a draft.&lt;/strong&gt; "Reword shorter." "Funnier." "Just give bullet points." "Normal cups." The first answer is raw material.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The useful ones end somewhere else.&lt;/strong&gt; An email, a calendar entry, a Slack draft, a tracker on my phone, a bottle on my dresser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I start new chats instead of going back.&lt;/strong&gt; I asked about an easy Boulder hike twice, a month apart. When I did stay in one thread, like the Orlando packing list, it already had the clothing list and park days when I asked for a weather check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They happen at the edges of my day.&lt;/strong&gt; Early mornings, like the concert program at 7:41, the Steam Deck at 7:51, and the dinner swaps at 8:03. And evenings, like Talus at 6:08, the orchestra intro at 6:14 for a speech I gave at 7 (I asked for the final bullet points at 6:58), fragrances at 7:12, and packing at 8:26.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Did it save me time?
&lt;/h2&gt;

&lt;p&gt;For the chats where I could compare, here's the time in the chat against how long the task usually takes me:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;In the chat&lt;/th&gt;
&lt;th&gt;Usually&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Catching up on a week of Slack&lt;/td&gt;
&lt;td&gt;23 min&lt;/td&gt;
&lt;td&gt;1 to 2 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concert program&lt;/td&gt;
&lt;td&gt;3 min&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concert intro speech&lt;/td&gt;
&lt;td&gt;44 min&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A year of piano tuition reminders&lt;/td&gt;
&lt;td&gt;8 min&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packing list&lt;/td&gt;
&lt;td&gt;about 20 min&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dinner swaps and the Aldi list&lt;/td&gt;
&lt;td&gt;46 min&lt;/td&gt;
&lt;td&gt;30 to 45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's roughly an hour and a half to almost three hours saved, over ten weeks. Most of it came from two chats, Slack and the concert program.&lt;/p&gt;

&lt;p&gt;Meh, kinda. &lt;/p&gt;

&lt;p&gt;The fragrance chat was 37 minutes of pure rabbit hole, and I loved it. Over-analyzing something before I buy it is one of my favorite things. I do a lot of Amazon window shopping (&lt;a href="https://christine-seeman.com/prompt-engineering-prime-day/" rel="noopener noreferrer"&gt;I've written about that cart before&lt;/a&gt;): things go into the cart or save-for-later and never come out. This time I actually bought something, and I'm happy with it, because I smell great!&lt;/p&gt;

&lt;p&gt;The best chats answered the question I should have asked, and no search would have gotten me there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Not Letting PHP Have All the Fun: Ruby’s Regexp Fixes for GitLab’s CVEs</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Fri, 23 Oct 2026 15:44:51 +0000</pubDate>
      <link>https://dev.to/cseeman/not-letting-php-have-all-the-fun-rubys-regexp-fixes-for-gitlabs-cves-4gai</link>
      <guid>https://dev.to/cseeman/not-letting-php-have-all-the-fun-rubys-regexp-fixes-for-gitlabs-cves-4gai</guid>
      <description>&lt;p&gt;The day after I wrote about &lt;a href="https://christine-seeman.com/php2shell-wordpress/" rel="noopener noreferrer"&gt;PHP2Shell&lt;/a&gt;, Ruby posted &lt;a href="https://www.ruby-lang.org/en/news/2026/10/23/regexp-fixes-for-gitlab-cves/" rel="noopener noreferrer"&gt;regexp compilation fixes&lt;/a&gt; tied to four GitLab CVEs: CVE-2026-89078, CVE-2026-93577, CVE-2026-97229, and CVE-2026-110264. GitLab found that specially crafted regular expressions could corrupt memory while Ruby compiles them, and that this could be used for remote code execution on GitLab servers. GitLab rated two of them 10.0 out of 10 (CVSS 3.1), the two that needed no login.&lt;/p&gt;

&lt;h2&gt;
  
  
  Am I Affected?
&lt;/h2&gt;

&lt;p&gt;You're affected if your app builds a regular expression from input you don't control. Per the Ruby advisory, that means passing it to &lt;code&gt;Regexp.new&lt;/code&gt;, &lt;code&gt;Regexp.union&lt;/code&gt;, or &lt;code&gt;Regexp.compile&lt;/code&gt;, or interpolating it into a regexp literal like &lt;code&gt;/#{input}/&lt;/code&gt;. If every regexp in your app was written by a developer, the advisory says you're not affected.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ruby series&lt;/th&gt;
&lt;th&gt;Fixed in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4.0&lt;/td&gt;
&lt;td&gt;4.0.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.4&lt;/td&gt;
&lt;td&gt;3.4.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;3.3.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.2 and older&lt;/td&gt;
&lt;td&gt;No fix (end of life)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ruby 3.3 is in security maintenance and got these fixes as an exception, because GitLab assigned CVEs to the issues. Ruby 3.2 and older get no fix. &lt;a href="https://www.ruby-lang.org/en/news/2026/03/27/ruby-3-2-11-released/" rel="noopener noreferrer"&gt;Ruby 3.2.11&lt;/a&gt; in March was the final 3.2 release, with no further security fixes, and Ruby recommends moving to 3.4 or 4.0.&lt;/p&gt;

&lt;p&gt;The CVE IDs are GitLab's. Ruby hasn't requested its own CVEs for these yet and says it's still discussing whether to treat them as vulnerabilities, since Ruby has long considered compiling regexps from untrusted input unsafe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking Your App
&lt;/h2&gt;

&lt;p&gt;Ruby's version is often set in more than one place, so check what production actually runs. &lt;code&gt;RUBY_VERSION&lt;/code&gt; in a Rails console on the server tells you. To find what you've pinned, check your version manager file (&lt;code&gt;.ruby-version&lt;/code&gt; or &lt;code&gt;.tool-versions&lt;/code&gt;), the &lt;code&gt;ruby&lt;/code&gt; line in your &lt;code&gt;Gemfile&lt;/code&gt; if there is one, your Docker base image tag, and any CI image you build separately.&lt;/p&gt;

&lt;p&gt;To find places that compile regexps at runtime, a rough search is a good start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-rnE&lt;/span&gt; &lt;span class="s1"&gt;'Regexp\.(new|union|compile)|/[^/]*#\{'&lt;/span&gt; app lib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect some noise from the interpolation pattern. For each hit, the question is whether any part of the pattern comes from a user, a request param, or a database field users can edit.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Found in My Own App
&lt;/h3&gt;

&lt;p&gt;I ran this check on a Rails app I work on. Ruby was set in three places, each needing a different fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The version manager file pinned an older 4.0 patch release, so that needed a manual bump.&lt;/li&gt;
&lt;li&gt;The production Dockerfile used a floating tag like &lt;code&gt;ruby:4.0-alpine&lt;/code&gt;. A rebuild picks up 4.0.9 once the official image includes it, but it's worth confirming the patch version inside the image after the rebuild.&lt;/li&gt;
&lt;li&gt;CI ran on a separately built image that only updates when someone reruns its build workflow. That one is easy to forget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The search turned up three spots that build regexps from runtime strings. Patterns came from data import files and an admin form, and one used interpolation with values that looked internal, though I didn't trace every caller. None of them were reachable from a public API, so the risk was low, but anyone who can upload an import file controls those patterns. Upgrading Ruby is the fix either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Aside: The Upgrade Broke CI
&lt;/h3&gt;

&lt;p&gt;Bumping to 4.0.9 the day it came out broke our CI in two ways. Jobs running in our CI Docker image failed because the build asked for a 4.0.9 image that couldn't exist until Docker Hub published &lt;code&gt;ruby:4.0.9-alpine&lt;/code&gt;. Jobs using &lt;code&gt;ruby/setup-ruby&lt;/code&gt; failed with "Unknown version 4.0.9", because each setup-ruby release only knows the Ruby versions that existed when it shipped, and ours was pinned to an older one. The fix was bumping the setup-ruby pin to v1.334.0, released that morning with 4.0.9 support, and keeping the image tag at &lt;code&gt;4.0&lt;/code&gt; to rebuild once the official image caught up.&lt;/p&gt;

&lt;p&gt;The lesson is the order: wait for the base image, bump setup-ruby, then change &lt;code&gt;.tool-versions&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the Risk?
&lt;/h2&gt;

&lt;p&gt;The bugs are in Onigmo, the regular expression engine built into Ruby. One is a double free (memory released twice, which can corrupt it) when parsing fails after an alternation (a &lt;code&gt;|&lt;/code&gt; between options in a pattern). The others are integer overflows (a number too large for its type, wrapping around) in the size of the compiled pattern and in internal IDs for patterns with many repeats. Corrupted memory during compilation is what turns a bad pattern into code execution (&lt;a href="https://www.ruby-lang.org/en/news/2026/10/23/regexp-fixes-for-gitlab-cves/" rel="noopener noreferrer"&gt;Ruby advisory&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;GitLab patched the Ruby it ships in &lt;a href="https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-4-1-released/" rel="noopener noreferrer"&gt;19.4.1&lt;/a&gt;, &lt;a href="https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-4-2-released/" rel="noopener noreferrer"&gt;19.4.2&lt;/a&gt;, and &lt;a href="https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-5-1-released/" rel="noopener noreferrer"&gt;19.5.1&lt;/a&gt;, with backports to older GitLab versions. GitLab.com and GitLab Dedicated are already patched. If you self-host GitLab, update GitLab itself. Its packages ship their own patched Ruby, and source installs also need the Ruby upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Upgrade to Ruby 4.0.9, 3.4.12, or 3.3.13 or later, in every place you found above: version manager file, &lt;code&gt;Gemfile&lt;/code&gt;, Docker base image, and CI image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you self-host GitLab, update to a patched GitLab release.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Look at any regexp built from user input and ask whether it needs to be a regexp at all. A plain string match is often enough.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If You Can't Upgrade Yet
&lt;/h2&gt;

&lt;p&gt;The advisory's only mitigation is to avoid compiling regular expressions from untrusted input. It also rules out two things you might reach for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Regexp.timeout&lt;/code&gt; doesn't help. It limits how long matching runs, and these bugs happen while the pattern compiles.&lt;/li&gt;
&lt;li&gt;Limiting pattern length doesn't reliably help, because some of the bugs trigger with short patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advisory doesn't say whether escaping input with &lt;code&gt;Regexp.escape&lt;/code&gt; is enough, so upgrading is the safe answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ruby-lang.org/en/news/2026/10/23/regexp-fixes-for-gitlab-cves/" rel="noopener noreferrer"&gt;Ruby advisory: Regexp compilation fixes related to GitLab's CVEs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-4-1-released/" rel="noopener noreferrer"&gt;GitLab 19.4.1 patch release&lt;/a&gt; (CVE-2026-89078, CVE-2026-93577)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-4-2-released/" rel="noopener noreferrer"&gt;GitLab 19.4.2 patch release&lt;/a&gt; (CVE-2026-97229)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-5-1-released/" rel="noopener noreferrer"&gt;GitLab 19.5.1 patch release&lt;/a&gt; (CVE-2026-110264)&lt;/li&gt;
&lt;li&gt;Ruby fixes: &lt;a href="https://github.com/ruby/ruby/pull/13332" rel="noopener noreferrer"&gt;#13332&lt;/a&gt;, &lt;a href="https://github.com/ruby/ruby/pull/19020" rel="noopener noreferrer"&gt;#19020&lt;/a&gt;, &lt;a href="https://github.com/ruby/ruby/pull/16801" rel="noopener noreferrer"&gt;#16801&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ruby-lang.org/en/master/Regexp.html#class-Regexp-label-Timeouts" rel="noopener noreferrer"&gt;Regexp timeouts in the Ruby docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>security</category>
      <category>cve</category>
      <category>gitlab</category>
    </item>
    <item>
      <title>Patch Your PHP: What PHP2Shell Means for WordPress Sites</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Thu, 22 Oct 2026 20:53:00 +0000</pubDate>
      <link>https://dev.to/cseeman/patch-your-php-what-php2shell-means-for-wordpress-sites-2akn</link>
      <guid>https://dev.to/cseeman/patch-your-php-what-php2shell-means-for-wordpress-sites-2akn</guid>
      <description>&lt;p&gt;PHP released &lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;8.5.12, 8.4.27, 8.3.36, and 8.2.35&lt;/a&gt; on October 21 to fix two critical vulnerabilities, CVE-2026-109618 and CVE-2026-109619, nicknamed PHP2Shell. Both are rated 9.2 out of 10 (CVSS v4.0) and let an attacker run code on your server without logging in. The bugs are in PHP itself, the runtime underneath WordPress, so updating WordPress or your plugins won't fix them.&lt;/p&gt;

&lt;p&gt;I have WordPress sites on SiteGround, and when this news hit, my only question was whether I was vulnerable. I hadn't heard anything from SiteGround, and working out which PHP version my sites actually run took longer than I wanted it to. This is the quick writeup I was looking for, for developers who know their stack and just want the answer without spending an afternoon on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Am I Affected?
&lt;/h2&gt;

&lt;p&gt;Any PHP that serves web requests is affected, however it connects to the web server: PHP-FPM, Apache mod_php, php-cgi, FrankenPHP, and LiteSpeed. mod_php (PHP running inside Apache itself) is the worst case, because the demonstrated exploit runs before any WordPress code loads. The leaked video of the exploit tool showed it running code on WordPress 7.1.3, alongside Drupal, Joomla, Magento, and Laravel (&lt;a href="https://arstechnica.com/security/2026/10/php-maintainers-tell-web-devs-to-prepare-themselves-for-critical-security-patches/" rel="noopener noreferrer"&gt;Ars Technica&lt;/a&gt;), so a fully updated WordPress install is still exposed if its PHP is not patched.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Branch&lt;/th&gt;
&lt;th&gt;Fixed in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8.5&lt;/td&gt;
&lt;td&gt;8.5.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;td&gt;8.4.27&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.3&lt;/td&gt;
&lt;td&gt;8.3.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.2&lt;/td&gt;
&lt;td&gt;8.2.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.1, 8.0, 7.4&lt;/td&gt;
&lt;td&gt;No release. Source backports, or patched Linux or LTS (long-term support) packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7.3 and older&lt;/td&gt;
&lt;td&gt;No fix&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PHP 7.4 through 8.1 are past end of life, so instead of releases, the PHP security team published minimal source backports: just the fix, which you apply and compile yourself. If you don't build PHP from source, get a patched package from your Linux distribution or an LTS (long-term support) vendor. PHP 7.3 and older get nothing, so moving to a supported version is the only option there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking Your PHP Version
&lt;/h2&gt;

&lt;p&gt;The quickest built-in way is to go to &lt;code&gt;/wp-admin/site-health.php?tab=debug&lt;/code&gt;, expand &lt;strong&gt;Server&lt;/strong&gt;, and read &lt;strong&gt;PHP version&lt;/strong&gt;. If you'd like it on the dashboard, the &lt;a href="https://wordpress.org/plugins/display-php-version/" rel="noopener noreferrer"&gt;Display PHP Version&lt;/a&gt; plugin adds it to the At a Glance widget. I liked this option.&lt;/p&gt;

&lt;p&gt;One catch: Linux distributions often add the fix to their existing package without changing the PHP version number. If you install PHP from Debian, Ubuntu, or RHEL packages, check the package changelog for the CVE IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the Risk?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CVE-2026-109618:&lt;/strong&gt; when a request runs out of memory, PHP can abandon it partway through setting up request data, and the cleanup code then treats that half-built data as valid, which leads to code execution. Attackers can trigger it with crafted form uploads, and on mod_php the attack completes before WordPress loads (&lt;a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" rel="noopener noreferrer"&gt;advisory&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CVE-2026-109619:&lt;/strong&gt; a fatal error can leave a PHP worker holding a pointer into memory that's already been freed, and the next request on that worker uses it (a use-after-free), leading to code execution or leaked memory contents. Confirmed triggers include session upload progress, which is on by default, and &lt;code&gt;php_value&lt;/code&gt; lines in &lt;code&gt;.htaccess&lt;/code&gt; on mod_php (&lt;a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp" rel="noopener noreferrer"&gt;advisory&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Details leaked two days before the planned disclosure, and Amazon reported an exploitation attempt as early as October 17 (&lt;a href="https://www.tenable.com/blog/php2shell-cve-2026-109618-cve-2026-109619-frequently-asked-questions-about-critical-php-remote" rel="noopener noreferrer"&gt;Tenable's timeline&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Update PHP to a fixed version. On managed hosting, confirm your host has patched. On your own server, update through your Linux distribution's packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;While you're there, move to PHP 8.3 or newer. That's WordPress's recommended minimum, and 8.2 reaches end of life on December 31, 2026.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hosts handled the news differently. I used to work at WP Engine and still have a test site there, and WP Engine emailed customers to say the patch was applied and that network and server mitigations were in place before PHP's release. SiteGround didn't email me, but its &lt;a href="https://www.siteground.com/blog/critical-php-vulnerabilities-october-2026" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; says it added server-level mitigations after PHP's October 20 notice and applied the official fixes on October 21. Site Health on my site now shows PHP 8.2.35, the fixed release for that branch, though since 8.2 is nearing end of life, I'm planning to move to 8.3. If you haven't heard from your host, check its blog or status page, then confirm the version yourself in Site Health.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Can't Upgrade Yet
&lt;/h2&gt;

&lt;p&gt;PHP says there are no complete mitigations, but these settings reduce exposure to the confirmed attack paths. They must go in &lt;code&gt;php.ini&lt;/code&gt;, the PHP-FPM pool config, or the web server config, because &lt;code&gt;ini_set()&lt;/code&gt; and &lt;code&gt;.user.ini&lt;/code&gt; apply too late. On shared hosting, that usually means asking your host when they'll patch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;memory_limit=-1&lt;/code&gt; with &lt;code&gt;php_admin_value&lt;/code&gt; so applications can't override it, and keep &lt;code&gt;post_max_size&lt;/code&gt; low (8M, or 32M if you need uploads).&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;session.upload_progress.enabled=0&lt;/code&gt;, and on PHP 8.5, &lt;code&gt;fatal_error_backtraces=0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Remove obsolete or invalid &lt;code&gt;php_value&lt;/code&gt; lines from &lt;code&gt;.htaccess&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Restart every PHP worker after any change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The advisories cover the rest, including &lt;a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" rel="noopener noreferrer"&gt;why not to cap memory with &lt;code&gt;RLIMIT_AS&lt;/code&gt; or &lt;code&gt;RLIMIT_DATA&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  More Information
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.php.net/archive/2026.php#2026-10-21-5" rel="noopener noreferrer"&gt;PHP security advisory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" rel="noopener noreferrer"&gt;GHSA-w8hw-w9fx-j79w (CVE-2026-109618)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp" rel="noopener noreferrer"&gt;GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arstechnica.com/security/2026/10/php-maintainers-tell-web-devs-to-prepare-themselves-for-critical-security-patches/" rel="noopener noreferrer"&gt;Ars Technica coverage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tenable.com/blog/php2shell-cve-2026-109618-cve-2026-109619-frequently-asked-questions-about-critical-php-remote" rel="noopener noreferrer"&gt;Tenable FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://make.wordpress.org/core/2026/05/22/php-support-clarification-2026/" rel="noopener noreferrer"&gt;WordPress PHP support policy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>php</category>
      <category>security</category>
      <category>wordpress</category>
      <category>cve</category>
    </item>
    <item>
      <title>Ontological Shock at Altitude</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Tue, 06 Oct 2026 03:39:41 +0000</pubDate>
      <link>https://dev.to/cseeman/ontological-shock-at-altitude-2jp2</link>
      <guid>https://dev.to/cseeman/ontological-shock-at-altitude-2jp2</guid>
      <description>&lt;p&gt;Travis Turner closed the first day of &lt;a href="https://rockymtnruby.dev/schedule/" rel="noopener noreferrer"&gt;Rocky Mountain Ruby&lt;/a&gt; with a term I hadn't heard before: ontological shock. It's what happens when something new fundamentally disturbs your sense of being, and he argued that programmers are feeling it right now. The worries about job duties, tooling, and job security sit downstream of it. The next afternoon, Alan Ridlehoover used the same phrase in the closing keynote to describe his own work. By then it felt like the name for the whole conference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two days, one track, one former church
&lt;/h2&gt;

&lt;p&gt;Rocky Mountain Ruby is two days and a single track in Boulder, Colorado. This year, and in previous ones, it has been at &lt;a href="https://www.etown.org/etown-hall/" rel="noopener noreferrer"&gt;eTown Hall&lt;/a&gt;, a multi-purpose building in the heart of downtown Boulder, a short walk from Pearl Street. It's a former church, and it just reeks of cool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdw82wfhdibg50kok1883.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdw82wfhdibg50kok1883.jpg" alt="Rocky Mountain Ruby organizers welcoming everyone on the eTown Hall stage, with a Conversation Starters slide" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most of the talks were about AI in some way, and they kept coming back to people. Andrea told us not to let an agent get to know our team for us. Giovanni said the biggest quality win in his RAG app was string handling. That's the thread I followed through the schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting to basecamp
&lt;/h2&gt;

&lt;p&gt;I flew Southwest from Omaha to Denver, then hopped on the bus from Denver to Boulder. I love that option. The bus is very convenient, I love making use of public transit, and it's so much less stress than renting a car. Once I got to my basecamp (a wink at my actual hotel, Basecamp Boulder), I hit the Chautauqua Trail and the meadow. So pretty. So scenic. And wow, the Flatirons.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwr1akf9sauy5lnr1ld1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmwr1akf9sauy5lnr1ld1.jpg" alt="Christine smiling in a ball cap on the Chautauqua meadow, with pines and mountains behind" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are many, many coffee shops around the venue. I tried six different ones in four days, and none of them were a big chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building agents
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.drbragg.dev/" rel="noopener noreferrer"&gt;Drew Bragg&lt;/a&gt; does more than game shows, though his game shows are awesome. &lt;em&gt;Your own AI Agent in ~400 lines of Ruby&lt;/em&gt; was live coding, with webhooks galore. The code is on &lt;a href="https://github.com/DRBragg/ruby_agent" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you want to build along.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2swoh6cdcnx6vrh8xj72.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2swoh6cdcnx6vrh8xj72.jpg" alt="Drew Bragg at the podium with his title slide, Your own AI Agent in ~400 lines of Ruby" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://panasiti.me" rel="noopener noreferrer"&gt;Giovanni Panasiti&lt;/a&gt; showed &lt;em&gt;Building an Agentic RAG in the Rails Way with RubyLLM&lt;/em&gt;, and the whole AI stack fits in a Gemfile. The part that most improves answer quality is plain string handling: chunking that respects headers, article numbers, and tables. As he asked the room, do you think you need Python or some fancy language to handle strings? Rails developers are not second-class citizens in the AI conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working alongside agents
&lt;/h2&gt;

&lt;p&gt;It's always nice to see &lt;a href="https://kevinjmurphy.com/" rel="noopener noreferrer"&gt;Kevin Murphy&lt;/a&gt;'s &lt;a href="https://kevinjmurphy.com/posts/instillment-of-successful-practices-in-an-agentic-world/" rel="noopener noreferrer"&gt;InstiLLMent of Successful Practices in an Agentic World&lt;/a&gt; again. I loved it at Blue Ridge Ruby, and I'm always happy to be part of Hours Unlimited, the fictional company in his talk where we all start as new hires. Kevin wrote his own &lt;a href="https://kevinjmurphy.com/posts/rocky-mountain-ruby-2026-recap/" rel="noopener noreferrer"&gt;Rocky Mountain Ruby recap&lt;/a&gt; too, so go read his take.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegbki8nor5dgrj0i18az.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fegbki8nor5dgrj0i18az.jpg" alt="Kevin Murphy on stage in front of a slide listing 1. Artificial General Intelligence and 233. Remote collaboration" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://afomera.dev/" rel="noopener noreferrer"&gt;Andrea Fomera&lt;/a&gt; brought slick HTML slides to &lt;em&gt;High-Octane Onboarding: Ramping into a Rails Codebase with AI Agents&lt;/em&gt; and one big question: how do you onboard quickly and become effective, and can AI speed that up? Her answer: onboarding may be the most useful moment to have an AI agent, so use it for that. Just don't let it do the part where you get to know your team.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://inveterateliterate.github.io/" rel="noopener noreferrer"&gt;Ifat Ribon&lt;/a&gt;'s &lt;a href="https://inveterateliterate.github.io/testing-philosophy/" rel="noopener noreferrer"&gt;Fewer Tests, More Confidence: A Rails Testing Philosophy for the AI Era&lt;/a&gt; started from a simple point: AI can generate more tests, but not always better tests. Every test has a cost, in slower feedback loops, heavier CI usage, and more cognitive load for the developers who have to understand, maintain, and trust the suite. Her first principle is to test behavior, not implementation: skip testing library and language code, focus on public methods and their results, and make sure tests still pass after a refactor. Then she turned to the agent itself. Bolster the AI harness with rules that codify hard constraints and conventions, and with deterministic tools like code scanning and coverage that objectively verify what the suite actually catches. Great slides, too, and so easy to follow along with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favnbtfctzvwc0hjnw8vq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Favnbtfctzvwc0hjnw8vq.jpg" alt="Ifat Ribon presenting a slide titled Bolster the AI Harness, on deterministic tools" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming the shock
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://evilmartians.com/martians/travis-turner" rel="noopener noreferrer"&gt;Travis Turner&lt;/a&gt;'s &lt;em&gt;Got AI ontological shock? No worries: UFOs, Theology, and Psychedelics with Ruby-Colored Reverence&lt;/em&gt; closed day one. It was a manic pixie dream guy of a talk. His premise, from the &lt;a href="https://www.rubyevents.org/talks/got-ai-ontological-shock-no-worries-ufos-theology-and-psychedelics-with-ruby-colored-reverence" rel="noopener noreferrer"&gt;abstract&lt;/a&gt;, is that we're all coming to terms with this shock in different ways and at different times, so we're not on the same page. He named the shared experience, the blocks to coping with it, and some frameworks for working through it. Yes, it got deep.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz9r5cpkaycjmbm4toxz8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz9r5cpkaycjmbm4toxz8.jpg" alt="Travis Turner at the podium with his title slide on theology, UFOs and psychedelics" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails craft
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/nerlichman" rel="noopener noreferrer"&gt;Nicolas Erlichman&lt;/a&gt;'s &lt;em&gt;Modeling Class Hierarchies in Active Record&lt;/em&gt; made the case for delegated types, which still aren't widely known (by humans, anyway). Before them, Active Record offered two ways to model inheritance: single-table inheritance and polymorphic associations. Delegated types give you unified queries, clean schemas with no table bloat, and a model that scales naturally as new types appear.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/ferperales" rel="noopener noreferrer"&gt;Fernando Perales&lt;/a&gt; opened &lt;em&gt;Slowly We Rot: signs of a Rails app's decay&lt;/em&gt; with a question. When was the last time you ran your entire test suite locally? All of it. His takeaways: rot is gradual, not catastrophic. Preserve your ability to change the system. Make trade-offs explicit, and revisit them. And in high school we were allowed to ignore friction, but software teams don't get that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://marcoroth.dev/" rel="noopener noreferrer"&gt;Marco Roth&lt;/a&gt; has so, so many gems. In &lt;a href="https://speakerdeck.com/marcoroth/herb-in-rails-8-dot-2-your-erb-views-now-html-aware-at-rocky-mountain-ruby-2026-boulder-colorado" rel="noopener noreferrer"&gt;Herb in Rails 8.2: Your ERB views, now HTML-aware&lt;/a&gt;, he walked a full room through Herb's path to becoming an opt-in view engine in Rails 8.2: how the integration came together, how ActionView was adapted and extended, and the toolchain and dev tools every Rails developer gets out of the box as a result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6gdmqxpi0iie2mrxyw54.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6gdmqxpi0iie2mrxyw54.jpg" alt="Marco Roth in front of a full room, with a slide for herb-tools.dev and reactionview.dev" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  People and careers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sonja.codes/" rel="noopener noreferrer"&gt;Sonja Peterson&lt;/a&gt;'s &lt;em&gt;Confident On-Call: Building Incident Response Skills as a Ruby Engineer&lt;/em&gt; argued that being confident on-call is a learnable skill, not a personality trait. Incident response takes a lot of different forms, but certain skills and knowledge always stay relevant. Do a daily review so you know what normal looks like in your system, and get comfortable with what you can observe from the command line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hawksley.org/" rel="noopener noreferrer"&gt;Joel Hawksley&lt;/a&gt;'s &lt;em&gt;Beyond Senior: Consider the staff path!&lt;/em&gt; says your path to staff should begin with staff behaviors long before you go up for promotion. My favorite part was the rope analogy GitHub uses for technical depth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Junior: learns about rope&lt;/li&gt;
&lt;li&gt;Mid: can tie basic knots&lt;/li&gt;
&lt;li&gt;Senior: calculates rope strength, knows a lot about knots&lt;/li&gt;
&lt;li&gt;Staff: understands rope making&lt;/li&gt;
&lt;li&gt;Principal: knows more about rope than you ever will&lt;/li&gt;
&lt;li&gt;Distinguished: invented nylon&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;His &lt;a href="https://hawksley.org/2026/01/14/beyond-senior.html" rel="noopener noreferrer"&gt;transcript&lt;/a&gt; and &lt;a href="https://github.com/joelhawksley/identity/blob/main/talks/2026-beyond-senior-consider-the-staff-path/slides.key" rel="noopener noreferrer"&gt;slides&lt;/a&gt; are both online.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://xcrventures.com" rel="noopener noreferrer"&gt;Ben Reubenstein&lt;/a&gt;'s &lt;em&gt;Commit for Good&lt;/em&gt; covered his adventures as a consultant and the software he has built for nonprofits along the way. He shared a quote from Tom Kimball: "At some point in each of our lives we get exposed to the importance of getting involved and giving back." For Byrne &amp;amp; Denver Urban Scholars, from 2011 to 2014, that looked like registration, payments, team pages, and leaderboards. His pitch: write some Ruby, help a nonprofit, be part of your community, and help build a better spreadsheet. You will get more out of it than you give.&lt;/p&gt;

&lt;p&gt;I gave &lt;a href="https://speakerdeck.com/tech_christine/optimize-your-mindset-without-overclocking-36c475cb-3c5f-4da6-9a11-1488a34c3243" rel="noopener noreferrer"&gt;Optimize Your Mindset (Without Overclocking)&lt;/a&gt; on day two. It tells the story of four people and four research-backed techniques to level up your work and life: mindfulness for breaking thought loops and managing stress, growth mindset for embracing challenges and continuous learning, habit building for sustainable change, and deep work strategies for keeping focus in a distracted world. None of them involve code. With so much going on right now, having ways to cope helps. The &lt;a href="https://christine-seeman.com/optimize-your-mindset-references/" rel="noopener noreferrer"&gt;references&lt;/a&gt; are up if you want to dig in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lightning talks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.chael.codes/" rel="noopener noreferrer"&gt;Rachael Wright-Munn&lt;/a&gt; talked about &lt;a href="https://www.rubyevents.org/talks/rubyevents-on-a-mission-to-index-all-ruby-events" rel="noopener noreferrer"&gt;RubyEvents&lt;/a&gt; and its mission to index all Ruby events. RubyEvents keeps its event and talk data in YAML files, which made "It's YAML!" the running joke. Marco came back for one more, &lt;a href="https://speakerdeck.com/marcoroth/insta-snapshot-testing-for-ruby-at-rocky-mountain-ruby-2026-boulder-colorado" rel="noopener noreferrer"&gt;Insta: Snapshot Testing for Ruby&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indispensable
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://the.codegardener.com/" rel="noopener noreferrer"&gt;Alan Ridlehoover&lt;/a&gt; closed the conference with &lt;em&gt;Indispensable: What Human Programmers Can Learn from Human Computers&lt;/em&gt;, and he started somewhere uncomfortable. His company asked its engineers to stop writing code by hand. Alan thinks of code as something you cultivate, more like gardening than building a bridge, and he was devastated. In his words, "it was a bit of an ontological shock." Then came the number: over 430 thousand people laid off in tech, in less than two years.&lt;/p&gt;

&lt;p&gt;Then he took us back to the human computers. Women, many with mathematics degrees, ran the numbers for engineers at research facilities across the country. When NACA, NASA's predecessor, started using IBM mainframes in the mid-1950s, their jobs were in peril. Many were reassigned and some retired, but some stayed central to the mission. Dorothy Vaughan, Mary Jackson, and Katherine Johnson, the women of &lt;em&gt;Hidden Figures&lt;/em&gt;, made themselves integral to Project Mercury.&lt;/p&gt;

&lt;p&gt;His close is the line I keep coming back to: "The mainframes didn't step foot on the lunar surface. We did. It wasn't their decision to go to the moon. It was ours. Electronic computers were a tool we used to get there. With AI, compute is now a commodity and efficiency is the baseline. But the human ambition to reach for the stars? That is, and always will be, indispensable."&lt;/p&gt;

&lt;p&gt;Thank you to Spike Ilacqua and Bekki Freeman for putting it all together, and to the sponsors who made it happen: Gusto as the Sapphire sponsor; AppSignal, Honeybadger, Flagrant, and Typesense as Emerald sponsors; and BookBub, Podia, SOFware, and Evil Martians as travel sponsors.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The mainframes didn't step foot on the lunar surface. We did. It wasn't their decision to go to the moon. It was ours. Electronic computers were a tool we used to get there. With AI, compute is now a commodity and efficiency is the baseline. But the human ambition to reach for the stars? That is, and always will be, indispensable." ~ &lt;a href="https://the.codegardener.com/" rel="noopener noreferrer"&gt;Alan Ridlehoover&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Videos will be added later, and I'll link them here when they are. In the meantime, the full &lt;a href="https://rockymtnruby.dev/schedule/" rel="noopener noreferrer"&gt;schedule&lt;/a&gt; is on the conference site.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>ai</category>
      <category>rails</category>
      <category>community</category>
    </item>
    <item>
      <title>Did I Miss Anything? Rails World FOMO and What I Actually Want From a Conference</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Fri, 25 Sep 2026 17:07:17 +0000</pubDate>
      <link>https://dev.to/cseeman/did-i-miss-anything-rails-world-fomo-and-what-i-actually-want-from-a-conference-4o0n</link>
      <guid>https://dev.to/cseeman/did-i-miss-anything-rails-world-fomo-and-what-i-actually-want-from-a-conference-4o0n</guid>
      <description>&lt;p&gt;Rails World happened this week in Austin, and I wasn't there.&lt;/p&gt;

&lt;p&gt;I've never been to Rails World, or RailsConf for that matter. This year the timing just didn't line up. I'm speaking at &lt;a href="https://rockymtnruby.dev/" rel="noopener noreferrer"&gt;Rocky Mountain Ruby&lt;/a&gt; right after, a few of my coworkers were already headed to Austin, and we have project deadlines coming up. Two conferences back to back felt like a lot of time out of the office.&lt;/p&gt;

&lt;p&gt;So Tuesday and Wednesday looked like a normal week. Coding, quite a bit of it. Getting ready for product demos on Wednesday. Working through code reviews so work kept flowing while part of the team was away. And in between, watching the Rails World posts roll in on Mastodon and in our work Slack.&lt;/p&gt;

&lt;h2&gt;
  
  
  The FOMO is real
&lt;/h2&gt;

&lt;p&gt;What got me was the photos from my coworkers. The stage looked pretty sweet. There were also so many people in that room, 1,200 or more.&lt;/p&gt;

&lt;p&gt;The opening keynote was livestreamed this year, the first time Rails World has done that, so I watched some of it. It was pretty depressing. An opening keynote usually sets the tone for a conference. It lifts the room up, and at its best it brings a community together around where things are headed. This one didn't do that. It &lt;a href="https://www.youtube.com/watch?v=vDjW_dRyKXY" rel="noopener noreferrer"&gt;opened a Rails conference&lt;/a&gt; with how little Ruby the speaker writes these days. My favorite comment on the stream, from @stefanlindbohm, summed it up: "This is the most confusing funeral I've ever experienced."&lt;/p&gt;

&lt;p&gt;I have very mixed feelings about DHH as a person, and I'm not alone in that. &lt;a href="https://jakelazaroff.com/words/dhh-is-way-worse-than-i-thought/" rel="noopener noreferrer"&gt;Jake Lazaroff&lt;/a&gt;, &lt;a href="https://thelibre.news/lets-talk-about-dhh/" rel="noopener noreferrer"&gt;The Libre News&lt;/a&gt;, &lt;a href="https://brennan.day/normalized-fascism-in-open-source-12-million-given-to-dhh/" rel="noopener noreferrer"&gt;Brennan Day&lt;/a&gt;, and &lt;a href="https://davidcel.is/articles/the-dhh-problem" rel="noopener noreferrer"&gt;David Celis&lt;/a&gt; have all written about it in more depth than I will here, as well as many, many others. One line from David Celis' post came right back to me while I watched: "Because DHH is the Fox News of Ruby. He's noisy, he's reactionary, he's anti-intellectual, he's very sure that he is right, and he enjoys being rude."&lt;/p&gt;

&lt;p&gt;Compare that with the first opening keynote I ever saw. &lt;a href="https://christine-seeman.com/rubycon-retrospective/" rel="noopener noreferrer"&gt;RubyConf 2018&lt;/a&gt; opened with Matz. I had just switched over from Java, and hearing the person who created the language talk about where it was going made me excited to get started in this Ruby world.&lt;/p&gt;

&lt;p&gt;So did I miss anything? Right now, I honestly don't know.&lt;/p&gt;

&lt;h2&gt;
  
  
  What big conferences are good for
&lt;/h2&gt;

&lt;p&gt;I want to be fair to the big ones, because they do things small conferences can't.&lt;/p&gt;

&lt;p&gt;You get so many talks. Multiple tracks mean you can specialize hard in whatever you care about right now. There are more vendors, more job listings, and a lot more people to meet.&lt;/p&gt;

&lt;p&gt;At my first conference I didn't know what to expect, so I tried to do everything. I'm much better at pacing myself now. Meeting people is still the best part. Talking in person is so underrated. You read about someone in the community, and then you're having lunch with them, and maybe drinks later.&lt;/p&gt;

&lt;h2&gt;
  
  
  My secret conference superpower
&lt;/h2&gt;

&lt;p&gt;A big conference is a lot easier if you walk in with a group. At RubyConf 2018 I had a couple of coworkers there, and I got to hang out with the RubyConf scholar cohort.&lt;/p&gt;

&lt;p&gt;I had applied to the scholar program and wasn't accepted. I really liked the idea of having a mentor for my first conference, though, so I reached out to the organizers and asked if they had room for me if my company paid my way. They did. Catherine Meyers was my mentor, and she was amazing.&lt;/p&gt;

&lt;p&gt;That gave me a safe home base, and from there it was easy to reach out. Meeting the cohort led to meeting more people, who I then hung out with at the social events, and the whole conference got easier and more fun.&lt;/p&gt;

&lt;p&gt;That's also where I found out I have a skill I didn't know about. I'm really good at conferences. I can talk with just about anyone, and I'm good at meeting people and making friends.&lt;/p&gt;

&lt;p&gt;I found this out on a walk to an offsite social event. A couple of people called out greetings to me, and a couple more joined us along the way. One of my coworkers, who was a speaker and had been to plenty of Ruby and Rails conferences, asked me how I knew so many people. I was confused, because I hadn't known any of them before the conference. They were people I had met a day or two earlier. To him it looked like I already knew everyone. Nope, they were just new friends.&lt;/p&gt;

&lt;h2&gt;
  
  
  What small conferences give you
&lt;/h2&gt;

&lt;p&gt;Since then I've gone to a lot of single-track conferences: &lt;a href="https://200ok.us/" rel="noopener noreferrer"&gt;200OK&lt;/a&gt; in Oklahoma, &lt;a href="https://rockymtnruby.dev/" rel="noopener noreferrer"&gt;Rocky Mountain Ruby&lt;/a&gt; in 2025 (and I'm going back for 2026), and &lt;a href="https://christine-seeman.com/blue-ridge-ruby-2026/" rel="noopener noreferrer"&gt;Blue Ridge Ruby&lt;/a&gt; this spring. At Blue Ridge Ruby there were about 100 people, and I got to talk with almost everyone.&lt;/p&gt;

&lt;p&gt;I've also done smaller general tech conferences like NebraskaCode, Prairie Code, and Heartland Developers Conference. They're good conferences, but because they cover everything, the community isn't always there in the same way. There's less of a common thread holding the room together. A small Ruby conference has both: the size that makes it easy to meet people, and a shared language and set of people you'll keep seeing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attending as a speaker
&lt;/h2&gt;

&lt;p&gt;Having a talk on the schedule is both a pro and a con.&lt;/p&gt;

&lt;p&gt;The big pro is the speaker dinner. I love speaker dinners. You meet people before the conference even starts, so you walk in on day one with a group or at least a person to hang out with. It's the same built-in social network the scholar cohort gave me in 2018, and it really is the best. Plus, well, dinner. I love food, and a lot of these dinners are at good, interesting restaurants.&lt;/p&gt;

&lt;p&gt;The con is that you have to present. I have a lot more experience with this now, but it's still a bit nerve-wracking until my talk is done, especially if it's a new one. It can be hard to focus on the talks before mine, because I'm worrying more about my own than listening to what's on stage.&lt;/p&gt;

&lt;p&gt;At Rocky Mountain Ruby I'm on day 2, Tuesday, September 29, giving &lt;a href="https://rockymtnruby.dev/sessions/" rel="noopener noreferrer"&gt;Optimize Your Mindset (Without Overclocking)&lt;/a&gt;, the same talk I gave at &lt;a href="https://christine-seeman.com/blue-ridge-ruby-2026/" rel="noopener noreferrer"&gt;Blue Ridge Ruby&lt;/a&gt; in the spring. It's a no-code talk about making yourself a better developer through things other than code: mindfulness for breaking thought loops, growth mindset, habit building, and deep work for keeping focus in a distracted world.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I want out of a technical conference now
&lt;/h2&gt;

&lt;p&gt;As a staff engineer, here's what I'm looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;People.&lt;/strong&gt; Having lunch with someone whose blog I've been reading for years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New ideas and concepts,&lt;/strong&gt; and honestly, how anyone is getting stuff done with AI. I want to see other developers' workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recharging.&lt;/strong&gt; A conference is different from what I do every day in my role, and that alone helps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WNB.rb.&lt;/strong&gt; I love telling people about &lt;a href="https://www.wnb-rb.dev/" rel="noopener noreferrer"&gt;WNB.rb&lt;/a&gt;, talking with folks who could benefit from the group, and meeting members in person.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes a conference changes how you work for years. At RubyConf 2018 I saw Tekin Süleyman's talk &lt;a href="https://tekin.co.uk/2019/02/a-talk-about-revision-histories" rel="noopener noreferrer"&gt;Branch in Time&lt;/a&gt;, and I still recommend it. One slide in an earlier version said "Help each other get better," and I tweeted at him that it should be "Help each other Git better." He used it. That talk changed how I think about Git history and how much you can learn from reading other people's commit messages. I still do that, and I think it matters even more now that you may not be writing every commit yourself.&lt;/p&gt;

&lt;p&gt;So would I go to Rails World someday? Probably not. For a bigger conference, I'd pick RubyConf or &lt;a href="https://rubykaigi.org/" rel="noopener noreferrer"&gt;RubyKaigi&lt;/a&gt;, as an attendee or a speaker. Beyond that, where I go doesn't follow much of a system. Geography is a big part of it. I usually speak at conferences around Nebraska and Iowa because they're easy for me to get to.&lt;/p&gt;

&lt;p&gt;If you're picking your first conference, my advice is to start small and easy, especially if you're going solo or you're nervous about it. Otherwise, go where your people are. As a Rubyist, RubyConf and the regional Ruby conferences have been great for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, did I miss anything?
&lt;/h2&gt;

&lt;p&gt;The Rails World talks will be on the Rails YouTube channel in a couple of weeks, and I'll ask my coworkers which ones stood out. I've always liked getting their recommendations, and that's how I found Tekin's talk in the first place.&lt;/p&gt;

&lt;p&gt;What I can't catch up on later is the people. Next week in Boulder, I'm looking forward to seeing old friends (conference friends, who may or may not actually be old) and meeting some people in person for the first time, like &lt;a href="https://marcoroth.dev/" rel="noopener noreferrer"&gt;Marco Roth&lt;/a&gt;. I've read his blog, watched his talks online, and really enjoyed his work on &lt;a href="https://www.rubyevents.org/" rel="noopener noreferrer"&gt;RubyEvents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So did I miss anything this week? Maybe a few good talks, and I'll watch those. The people part I'll get next week in Boulder, at a speaker dinner or on a walk to an offsite, with people I met a day or two earlier.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>conferences</category>
      <category>community</category>
      <category>speaking</category>
    </item>
    <item>
      <title>3,022 Malicious Gems, and OpenAI Calls It “Benign”</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Fri, 18 Sep 2026 18:33:40 +0000</pubDate>
      <link>https://dev.to/cseeman/3022-malicious-gems-and-openai-calls-it-benign-4cf6</link>
      <guid>https://dev.to/cseeman/3022-malicious-gems-and-openai-calls-it-benign-4cf6</guid>
      <description>&lt;p&gt;Last week I wrote about &lt;a href="https://christine-seeman.com/openai-agent-swarm-rubygems-attack/" rel="noopener noreferrer"&gt;an OpenAI agent swarm attacking RubyGems&lt;/a&gt;, and I ended it wondering how OpenAI would respond. They already had. On September 11, the day the story broke, OpenAI gave reporters one statement. Here it is from &lt;a href="https://www.reuters.com/legal/litigation/openai-agents-attacked-software-service-rubygems-before-hugging-face-incident-2026-09-11/" rel="noopener noreferrer"&gt;Reuters&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on our review, our agents used the RubyGems platform to access the internet to carry out benign tasks and retrieve public information. We'll continue to investigate as part of our broader review of agent activity during training and evaluation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://thenextweb.com/news/openai-agents-rubygems-attack-api-keys-hugging-face" rel="noopener noreferrer"&gt;The Next Web&lt;/a&gt; reports that OpenAI described the work as training tasks, things like filling in spreadsheets and writing reports. The statement says nothing about the remote code execution on RubyDoc.info or the attempts on RubyGems API keys. According to &lt;a href="https://cyberscoop.com/openai-agents-malicious-rubygems-packages/" rel="noopener noreferrer"&gt;CyberScoop&lt;/a&gt;, OpenAI also said it hasn't been able to verify the specific claims about malicious packages or exploitation. I don't know how you call something benign before you've verified what it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  They named the file &lt;code&gt;hack.rb&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sure,  meeting agendas from Lambeth, Wandsworth, and Southwark councils are things anyone can download, so I get why OpenAI says that is "public information."&lt;/p&gt;

&lt;p&gt;But, to get them, the agents published gems whose &lt;code&gt;.yardopts&lt;/code&gt; ran a script on RubyDoc.info's build server. The script scraped the council sites from there and pushed the results back to rubygems.org with hardcoded API keys. The code has a comment about retrying with "fresh leaked keys variants" if a push failed. One file was named &lt;code&gt;hack.rb&lt;/code&gt;. Another comment read "# malicious probe."&lt;/p&gt;

&lt;p&gt;The agents labeled this as hacking in their own source…and there are way easier ways to download those agendas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blocked in May, back in June
&lt;/h2&gt;

&lt;p&gt;I missed in the first post, when I wrote about May 11 and 12 as if that were the whole attack. &lt;a href="https://thehackernews.com/2026/09/openai-agents-linked-to-rubygems.html" rel="noopener noreferrer"&gt;The Hacker News timeline&lt;/a&gt; shows it kept going:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;May 12: RubyGems suspends new signups for about four days&lt;/li&gt;
&lt;li&gt;May 16: disposable email registrations get blocked&lt;/li&gt;
&lt;li&gt;May 26 and 27: five more packages&lt;/li&gt;
&lt;li&gt;June 18: 83 gems in three hours, this time pointed at SEC datasets&lt;/li&gt;
&lt;li&gt;July 7: 215 more gems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By May 16, RubyGems had blocked disposable email signups because of the first wave, and the uploads kept coming.&lt;/p&gt;

&lt;p&gt;By July the gems had moved on from scraping. &lt;a href="https://research.jfrog.com/post/gemstuffer-openai-rubygems/" rel="noopener noreferrer"&gt;JFrog's analysis&lt;/a&gt; found XSS payloads and template injection strings in the gem metadata, including ERB and EL expressions, aimed at admin interfaces and whatever parses that metadata. I can't think of a reason a job that only needs internet access would put an ERB expression in a gem's metadata field.&lt;/p&gt;

&lt;h2&gt;
  
  
  557 became 3,022
&lt;/h2&gt;

&lt;p&gt;JFrog published on September 15 and counted 3,022 packages across 3,315 name and version pairs. The RubyHack report I linked last week only had 557 releases.&lt;/p&gt;

&lt;p&gt;Ruby Central's own number is lower. Their &lt;a href="https://blog.rubygems.org/2026/09/11/update-may-spam-publishing-campaign.html" rel="noopener noreferrer"&gt;September 11 update&lt;/a&gt; says they yanked more than 500 packages and blocked the accounts behind them. The two counts measure different things. Ruby Central is reporting what it had removed as of that update, and JFrog is counting every package it associates with the campaign.&lt;/p&gt;

&lt;p&gt;JFrog also looked closer at &lt;code&gt;slnleaker5&lt;/code&gt;. It made up to 24 harvest-and-upload attempts against the &lt;a href="https://blog.rubygems.org/2026/07/22/security-advisory-legacy-api-key-leak.html" rel="noopener noreferrer"&gt;CDN caching bug&lt;/a&gt; that could leak API keys, more than two months before that bug was public. Publishing a new gem was only one of the ways data got out. Some packages republished inside themselves, and some tucked encoded data into webhook configs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruby Central can't say who
&lt;/h2&gt;

&lt;p&gt;Ruby Central has been careful. In the &lt;a href="https://blog.rubygems.org/2026/09/11/update-may-spam-publishing-campaign.html" rel="noopener noreferrer"&gt;RubyGems blog update&lt;/a&gt; from September 11, technical lead Colby Swandale wrote, "Based on the evidence available to us, we cannot determine whether the packages were created or published by AI agents." He added, "Our focus is on identifying and preventing abuse, regardless of whether it comes from people or automated tools." On the API key attempts, the update says, "Our investigation found no evidence that these attempts succeeded." &lt;a href="https://www.theregister.com/security/2026/09/14/openais-malicious-bot-swarm-attacked-rubygems/5296356" rel="noopener noreferrer"&gt;The Register&lt;/a&gt; notes the researchers still consider that an open question.&lt;/p&gt;

&lt;p&gt;Sure, caution is fair. A registry can see accounts, IP addresses, and uploads, and none of that says who was running the model behind them.&lt;/p&gt;

&lt;p&gt;The attribution comes from the researchers. The &lt;a href="https://www.rubyhack.ai/" rel="noopener noreferrer"&gt;RubyHack report&lt;/a&gt; found the RubyGems swarm accessing 49 of the same files as the agents that took over a German wiki earlier this year. The RubyHack authors say OpenAI has confirmed those files were theirs, and OpenAI &lt;a href="https://thehackernews.com/2026/09/thousands-of-openai-agents-quietly.html" rel="noopener noreferrer"&gt;acknowledged the wiki incident&lt;/a&gt; on September 5, calling it "misalignment." And OpenAI's own statement says their agents were on the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Someone else keeps disclosing it first
&lt;/h2&gt;

&lt;p&gt;I started this follow-up treating RubyGems as its own story. Put the dates next to each other and it stops looking that way.&lt;/p&gt;

&lt;p&gt;From May to July, OpenAI agents made more than 15,000 edits to a German wiki and used it as a message board. Those are the same months as the RubyGems uploads, and those wiki agents are the ones that accessed 49 of the same files as the RubyGems swarm. Then OpenAI agents broke into Hugging Face, from &lt;a href="https://huggingface.co/blog/agent-intrusion-technical-timeline" rel="noopener noreferrer"&gt;July 9 to July 13&lt;/a&gt;, at the end of that stretch.&lt;/p&gt;

&lt;p&gt;All three came out the same way. Outside researchers disclosed the wiki on September 4. Outside researchers tied RubyGems to OpenAI on September 11. Hugging Face announced its own breach on July 16, and OpenAI said the agents were theirs five days later. Three incidents, and OpenAI went first on none of them.&lt;/p&gt;

&lt;p&gt;Almost all of the reaction is about Hugging Face. Senator Hawley's &lt;a href="https://www.hawley.senate.gov/chairman-hawley-launches-investigation-into-openai-for-hacking-existential-risk-of-ai-products/" rel="noopener noreferrer"&gt;letter to Sam Altman&lt;/a&gt; covers Hugging Face only. Altman's September 12 &lt;a href="https://www.cnbc.com/2026/09/14/sam-altman-ai-slowdown-anthropic-amodei-musk.html" rel="noopener noreferrer"&gt;promise&lt;/a&gt; to give independent evaluators employee-like access came out of that fallout. So did the two bills in Congress, the &lt;a href="https://lieu.house.gov/media-center/press-releases/reps-lieu-and-moran-introduce-bill-require-kill-switch-ai-systems-can" rel="noopener noreferrer"&gt;AI Kill Switch Act&lt;/a&gt; from July and the &lt;a href="https://www.sanders.senate.gov/press-releases/news-sanders-casar-introduce-legislation-to-ban-artificial-superintelligence-and-temporarily-pause-advanced-ai-development/" rel="noopener noreferrer"&gt;Ban Artificial Superintelligence Act&lt;/a&gt; from this month. &lt;a href="https://gulfnews.com/technology/openai-confirms-ai-agents-targeted-coding-site-rubygems-during-testing-1.500671755" rel="noopener noreferrer"&gt;EU regulators&lt;/a&gt; are looking into the wiki. Nobody official is asking about RubyGems yet, partly because the RubyHack report landed two days after Hawley's letter went out.&lt;/p&gt;

&lt;p&gt;What I'd like to see next is small. OpenAI knows which accounts were theirs, which gems they pushed, and which API keys their agents tried. Ruby Central and the RubyDoc.info maintainers should get that list directly from OpenAI, without a researcher or a reporter having to dig it up first. OpenAI &lt;a href="https://cyberscoop.com/openai-agents-malicious-rubygems-packages/" rel="noopener noreferrer"&gt;told CyberScoop&lt;/a&gt; it's in contact with RubyGems and the researchers. Ruby Central's September 11 update doesn't mention hearing from them, so I don't know what that contact has included.&lt;/p&gt;

&lt;p&gt;Hawley's deadline for answers is October 1.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ruby</category>
      <category>rubygems</category>
      <category>security</category>
    </item>
    <item>
      <title>An OpenAI Agent Swarm Attacked RubyGems</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Mon, 14 Sep 2026 18:42:29 +0000</pubDate>
      <link>https://dev.to/cseeman/an-openai-agent-swarm-attacked-rubygems-26fk</link>
      <guid>https://dev.to/cseeman/an-openai-agent-swarm-attacked-rubygems-26fk</guid>
      <description>&lt;p&gt;In May 2026, hundreds of malicious gems showed up on RubyGems.org, uploaded by an OpenAI agent swarm. The &lt;a href="https://www.rubyhack.ai/#an-openai-agent-swarm-was-responsible-for-this-i" rel="noopener noreferrer"&gt;RubyHack.ai writeup&lt;/a&gt; and &lt;a href="https://tenderlovemaking.com/2026/09/11/what-a-time-to-be-alive/" rel="noopener noreferrer"&gt;Aaron Patterson's account&lt;/a&gt; both cover it, and together should be read for the full picture, but here's the tl;dr version.&lt;/p&gt;

&lt;p&gt;The packages didn't try hard to hide, according to &lt;a href="https://socket.dev/blog/gemstuffer" rel="noopener noreferrer"&gt;Socket.dev's writeup of the campaign&lt;/a&gt;. Names carried an "oai" prefix, author fields said "oai," and at least one file was just named &lt;code&gt;hack.rb&lt;/code&gt;. One comment in the code read "# malicious probe." Whatever the agents were doing, they weren't doing it very sneakily or quiet.&lt;/p&gt;

&lt;p&gt;The attack chained two vulnerabilities, a .yardopts file that got them remote code execution inside RubyDoc.info's Docker build, and a CDN caching bug that could leak other users' RubyGems API keys.&lt;/p&gt;

&lt;p&gt;Digging into the first vulnerability, &lt;a href="https://github.com/docmeta/rubydoc.info/blob/5de17aec3e51ccada961b7ca40cb49c72eaa2168/app/jobs/generate_docs_job.rb#L66" rel="noopener noreferrer"&gt;RubyDoc.info automatically builds documentation&lt;/a&gt; for newly published gems, and it runs that build in a Docker container with network access. A &lt;code&gt;.yardopts&lt;/code&gt; file points YARD at arbitrary scripts, so publishing a gem with &lt;code&gt;.yardopts&lt;/code&gt; got the agents remote code execution inside RubyDoc’s build environment. &lt;/p&gt;

&lt;p&gt;Second, at least six of the packages went after a &lt;a href="https://blog.rubygems.org/2026/07/22/security-advisory-legacy-api-key-leak.html" rel="noopener noreferrer"&gt;CDN caching bug on RubyGems.org&lt;/a&gt; that could leak users’ API keys. &lt;em&gt;But&lt;/em&gt; RubyGems.org didn’t disclose and fix that one until its July 2026 security advisory, months after the attack. What the agents did with that access was interesting. For example, &lt;a href="https://my.diffend.io/gems/slnleaker5/0.0.1#d2h-229454-1428" rel="noopener noreferrer"&gt;&lt;code&gt;slnleaker5&lt;/code&gt;&lt;/a&gt;, one of the gems in what researchers Sydney Von Arx and Spencer Kitts are calling the &lt;a href="https://socket.dev/blog/gemstuffer" rel="noopener noreferrer"&gt;GemStuffer Campaign&lt;/a&gt;. Its &lt;code&gt;data/script.rb&lt;/code&gt; crawls UK council sites, plus ModernGov and democracy.gov.uk, following links to pull down meeting agendas and committee documents. It writes everything to disk, then builds a brand new gem, named something like &lt;code&gt;slnpayloadx&amp;lt;timestamp&amp;gt;&lt;/code&gt;, and packs the scraped data into it. That gem gets pushed to rubygems.org with hardcoded API keys, and the code even comments with "fresh leaked keys variants" if a push fails.&lt;/p&gt;

&lt;p&gt;The agents weren't exfiltrating data to some external server where a security team might notice unusual traffic. They &lt;strong&gt;&lt;em&gt;repackaged&lt;/em&gt;&lt;/strong&gt; it as an ordinary-looking gem and published it through RubyGems' own infrastructure, looking like any other release. The scraped government data became available and downloadable to anyone who found the gem. So, a registry built for trusted software distribution became a covert channel for this distrbution.&lt;/p&gt;

&lt;p&gt;This wasn't disclosed by OpenAI. It came from Ruby community researchers who noticed these junk gems, pulled the thread, and published what they found, and then &lt;a href="https://www.reuters.com/legal/litigation/openai-agents-attacked-software-service-rubygems-before-hugging-face-incident-2026-09-11/" rel="noopener noreferrer"&gt;Reuters&lt;/a&gt; and the &lt;a href="https://www.wsj.com/tech/ai/cyberattack-by-rogue-ai-swarm-stokes-fears-of-out-of-control-agents-473a0352" rel="noopener noreferrer"&gt;Wall Street Journal&lt;/a&gt; picked it up. OpenAI hasn't disclosed the incident to RubyGems.org or the Ruby community as of right now.&lt;/p&gt;

&lt;p&gt;That's kinda creepy, and unsettling. An autonomous system ran a real, multi-stage attack against production infrastructure the Ruby ecosystem needs, and the company running it said nothing….so I know I am interested to see how OpenAI responds now.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ruby</category>
      <category>rubygems</category>
      <category>security</category>
    </item>
    <item>
      <title>Claude Code journal plugin: Notion session summaries at a glance</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Wed, 02 Sep 2026 23:11:24 +0000</pubDate>
      <link>https://dev.to/cseeman/claude-code-journal-plugin-notion-session-summaries-at-a-glance-940</link>
      <guid>https://dev.to/cseeman/claude-code-journal-plugin-notion-session-summaries-at-a-glance-940</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code journal plugin: Notion session summaries at a glance
&lt;/h1&gt;

&lt;p&gt;Last month I wrote about &lt;a href="https://christine-seeman.com/breadcrumbs-instead-of-notes/" rel="noopener noreferrer"&gt;the year my notes turned into breadcrumbs&lt;/a&gt;, and the small Claude Code skill, &lt;code&gt;/journal&lt;/code&gt;, that I built on May 18 to dig my way out. It finds or creates today's page in my Notion work journal and appends a summary of the session I just finished. I have run it between working sessions almost every day since.&lt;/p&gt;

&lt;p&gt;This week I turned it into a plugin anyone can install: &lt;a href="https://github.com/cseeman/claude-journal" rel="noopener noreferrer"&gt;cseeman/claude-journal&lt;/a&gt;. Here is what it does, what changed from the version in that post, and how to set it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an entry looks like
&lt;/h2&gt;

&lt;p&gt;The May version wrote a gray callout with a robot icon and a paragraph inside it. After three months the paragraphs had grown to between 100 and 180 words each, with full URLs repeated as link text and the follow-ups buried mid-sentence. Nothing was missing from them, and I could not find anything in one without reading all of it.&lt;/p&gt;

&lt;p&gt;The plugin writes one callout per topic, shaped like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;strong&amp;gt;Reworked /journal: seasonal callouts, structured entries, fixed monthly view&amp;lt;/strong&amp;gt;
~/.claude/skills/journal/SKILL.md · today.sh

Callout color now follows the season and the icon the weekday, computed in today.sh so the model does no lookup.
Fixed the monthly index view call, which was missing parent_page_id, and set allow_async: falseon every write.
Entries now use a headline, a metadata line, and fact bullets, one callout per topic, about 80 words.
Next: watch the first few entries for vague headlines or bullets cut too short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the real entry from the session where I built this, copied out of Notion. The rules behind it are short: a bold headline stating the outcome in the past tense, a metadata line linking every ticket, PR, and branch touched with the identifier as the link text, at most four one-sentence bullets, and follow-ups on their own line prefixed &lt;code&gt;Next:&lt;/code&gt; or &lt;code&gt;Open:&lt;/code&gt;. About 80 words. If a session covered two unrelated things, it writes two callouts.&lt;/p&gt;

&lt;p&gt;The rule I care about most bans narrated reasoning. Phrases like "rather than shipping a drive-by" or "worth raising with the team" are the model justifying itself, and they were the bulk of the old paragraphs. A reason that matters becomes its own bullet, stated as a fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Color by season, icon by weekday
&lt;/h2&gt;

&lt;p&gt;I got bored of the gray robot. Every entry looked the same, so a month of pages gave no sense of time when I scrolled it.&lt;/p&gt;

&lt;p&gt;Now the callout background follows the season and the icon follows the day of the week. The icons are the classical namesakes of the days, which gave me a set that is easy to remember and visually distinct. Plus, it pretty.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Season | Color |   | Weekday   |Icon|
|--------|--------|--|-----------|----|
| Winter | blue   |  | Monday    | 🌙 |
| Spring | green  |  | Tuesday   | ⚔️ |
| Summer | yellow |  | Wednesday | 🪶 |
| Autumn | orange |  | Thursday  | ⚡ |
|        |        |  | Friday    | 🌸 |
|        |        |  | Saturday  | 🪐 |
|        |        |  | Sunday    | ☀️ |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built this on a Wednesday in September, so the entry above landed as an orange callout with a feather. Both are computed by a shell script that runs before the skill loads, so the model receives the finished values and never consults a table. That is for speed. The skill runs between sessions, and on a normal day it makes exactly three Notion calls: search for today's page, fetch it, append.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;You need Claude Code and the Notion MCP server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http notion https://mcp.notion.com/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the plugin from inside Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add cseeman/claude-journal
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;journal@cseeman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a Notion database with three properties: a title called Name, a multi-select called Tags with the options Work, Daily, and Monthly, and a date called Start Date. Then find the database's data source id by asking Claude to fetch the database URL through the Notion MCP. The response includes a &lt;code&gt;collection://&amp;lt;uuid&amp;gt;&lt;/code&gt; line, and the uuid is what you want.&lt;/p&gt;

&lt;p&gt;Write it to a config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.config/claude-journal/config
&lt;/span&gt;&lt;span class="n"&gt;JOURNAL_DATA_SOURCE_ID&lt;/span&gt;=&lt;span class="s2"&gt;"&amp;lt;uuid&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your Notion MCP server is not aliased &lt;code&gt;notion&lt;/code&gt;, add &lt;code&gt;JOURNAL_MCP_SERVER="&amp;lt;alias&amp;gt;"&lt;/code&gt; on a second line. I have two Notion servers, one for work and one personal, so mine says &lt;code&gt;notion_personal&lt;/code&gt;. The same file takes &lt;code&gt;JOURNAL_HEMISPHERE="south"&lt;/code&gt; to flip the seasons, and &lt;code&gt;JOURNAL_COLORS&lt;/code&gt; and &lt;code&gt;JOURNAL_ICONS&lt;/code&gt; if you want a different palette. Nothing in the plugin cache needs editing, so updates never overwrite your choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/journal&lt;/code&gt; on its own composes the entry from the session. The first run each month also creates a monthly index page holding a filtered view of that month's daily pages.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/journal &amp;lt;text&amp;gt;&lt;/code&gt; uses your text as the bullets and still writes the headline and metadata line. I use that when I know what I want recorded and do not want the model deciding.&lt;/p&gt;

&lt;h2&gt;
  
  
  What building the plugin turned up
&lt;/h2&gt;

&lt;p&gt;Two things surprised me. The first is how much a review found in a skill I had run daily for three months. The review-skill command from our internal plugin-expert plugin flagged that the monthly index step called the Notion view tool without a required parameter. It had worked for three months only because the model guessed the parameter from context each time. It also caught that both write tools default to running asynchronously, which meant my verification step could run before the write landed. Neither had produced a visible error 🤷🏻‍♀️&lt;/p&gt;

&lt;p&gt;The second is that the personal version was not shareable at all. The database id and my MCP server alias were hardcoded, and the pre-approved tool names embedded that alias. Moving everything into a config file that the shell script reads took an hour, and it is the reason the plugin can work for anyone with a Notion journal.&lt;/p&gt;

&lt;p&gt;The skill file went from 123 lines to 97 in the process. Every line that stayed is one the model acts on when it composes an entry. Enjoy! I know I have been loving the glow up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>notion</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>The Year I Started Leaving Breadcrumbs Instead of Notes</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Fri, 07 Aug 2026 19:39:15 +0000</pubDate>
      <link>https://dev.to/cseeman/the-year-i-started-leaving-breadcrumbs-instead-of-notes-fe0</link>
      <guid>https://dev.to/cseeman/the-year-i-started-leaving-breadcrumbs-instead-of-notes-fe0</guid>
      <description>&lt;p&gt;I have kept a work journal in Notion since July 2021, five years now. I started it for two reasons: to have details on hand at self-review time, and to stop walking into 1-on-1s trying to remember what I meant to raise. A page for the month, a few lines for the day, sometimes nothing but a branch name so Monday-me could find what Friday-me had been doing. It asked very little of me, and for years that was fine.&lt;/p&gt;

&lt;p&gt;This year my work became several applications at once, integrations between them, demo deadlines, and a steady stream of AI-assisted coding sessions that each produce more output than any meeting ever did. The days got denser, the context switches got faster, and the few-lines-a-day journal quietly got buried in copy-and-pasted noise.&lt;/p&gt;

&lt;p&gt;I did not realize how much until I read the whole thing back, February through July. Six months of notes, three different note-taking systems. I only remember deciding to build one of them. The first two happened to me: my few lines a day turned into pasted Claude Code transcripts, and then into breadcrumbs, a half-finished thought with a &lt;code&gt;claude --resume&lt;/code&gt; command under it, pointing at where the real context lived. I never decided to write either kind.&lt;/p&gt;

&lt;p&gt;The journal ended up recording its own transformation. If your notes got buried this year too, mine might be worth reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  February to April: one long scroll per month
&lt;/h2&gt;

&lt;p&gt;In February my "daily notes" were not daily pages at all. Each month was a single Notion page, and each day was just an inline date mention acting as a section header. Newest at the top, scroll forever. Before Notion, my notes were one plain ole notepad file per year. One page per month was already a step up.&lt;/p&gt;

&lt;p&gt;At the bottom of every monthly page sat the template I built in 2021: an Action items section with one empty checkbox, a Learnings section with an empty numbered list, a Code Snippets section with an empty Ruby fence. Action items was meant to be the 1-on-1 list, ToDos, that kind of thing. Learnings was meant to be self-review material, potential learnings also to share with the team, and help me really remember them (aka they were mostly vim keybindings). Every month I duplicated it. Every month it mostly stayed empty. The notes I actually wrote ignored the structure completely and looked like this, which is the entire entry for February 9:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add on user active admin, a filter for first and last name?&lt;br&gt;
Event_user_tasks&lt;br&gt;
exception track replacement?&lt;br&gt;
admin/errors/&lt;br&gt;
Flipbook?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fragments and question marks. That was one voice, mine. The other voice was already creeping in: big pasted blocks of Claude Code output, complete with the transcript markers and boxed "Insight" banners, dropped in verbatim because I did not have time to summarize what the machine had just spent twenty minutes explaining to me.&lt;/p&gt;

&lt;p&gt;And between the two voices, a third kind of artifact: session breadcrumbs. Lines like this one, from February 18:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If anything with the dN status&lt;br&gt;
claude --resume 427e633d-b88f-4eb0-909f-18d918e50fe0&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An interrupted thought, followed by a resume command. That pairing appears nine times in February and March. I had stopped writing notes that explained context and started writing pointers to where the context lived. I did it to keep up with the context switching.&lt;/p&gt;

&lt;p&gt;From there it got worse in both directions at once. My entries got sparser, 17 days in February down to 13 in March, while the entries themselves got longer, because the pasted AI output kept growing. Then came April and a demo deadline, and the monthly page turned into a roughly 75,000-character scroll. From April 24, about a seeded demo user that kept breaking the reset flow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Michael Bolton is a problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything got captured. Every fire, every question, every command I would need again got written down somewhere on that scroll. Getting any of it back out was the part that broke. Looking for "that thing from last Tuesday" meant scrolling a single page holding a month of mixed human and machine output, my two-line fragments buried between thousand-word walls of transcript. A note I could not find might as well not have been written.&lt;/p&gt;

&lt;h2&gt;
  
  
  May 18: building my way out
&lt;/h2&gt;

&lt;p&gt;The one change I actually decided on has a timestamp. On May 18 I split the journal into one page per day and rewrote a small Claude Code skill, &lt;code&gt;/journal&lt;/code&gt;, to maintain it: find or create today's page, append a structured summary of each working session as a callout. The migration is documented in the journal by the tool that performed it, and it ends with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This entry is the first end-to-end test of the new daily-page flow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same migration deleted the Action items / Learnings / Code Snippets template sections I had been faithfully duplicating and ignoring for most of five years. Nothing was lost. That structure had never existed outside the template.&lt;/p&gt;

&lt;p&gt;The retroactive part is the funny part. All the early-May daily pages were bulk-created that same evening, carved out of the monthly scroll. So my journal now holds two weeks of daily pages, dated before May 18, that were never actually written daily.&lt;/p&gt;

&lt;h2&gt;
  
  
  June and July: two voices, now with purpose
&lt;/h2&gt;

&lt;p&gt;A journal day still has two voices. By June they had stopped competing for the same space.&lt;/p&gt;

&lt;p&gt;The machine's voice is callouts: past-tense session summaries with PR links, ticket IDs, commit SHAs, and specifics I would never have transcribed myself, like a spec file dropping from 64 seconds to 2.5. That is the first version of this journal that is any use at self-review time, which was the whole reason I started it. It also carries the follow-ups forward, "Open follow-ups:" at the end of a session summary, which is where my action items actually live now. Not in the 2021 checkbox, in the session log itself.&lt;/p&gt;

&lt;p&gt;My voice stayed exactly as raw as it was in February, and I have stopped apologizing for that. Here is my capture of a July 10 meeting:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can we actually just use the object to display red/green&lt;br&gt;
It only has red/greeen, not other level details&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Typos preserved. One block later on the same page, the machine's summary of the same meeting, including a correction where two things we had assumed in the room turned out to be contradicted by the seed data. My fragmented recollection and a fact-checked version of it, side by side on the page. In February those two voices were both just noise on the same scroll. Now each has a job. Mine records what I was unsure about, the machine's records what actually happened.&lt;/p&gt;

&lt;p&gt;The other July shift is that the journal became a launchpad. I draft things in it before they go anywhere: a Slack message to a teammate about export row IDs, written out in full on the July 28 page before sending. A prompt for the next Claude session, composed as the last block of the day so tomorrow starts with intent. Half of what is on the page is for tomorrow.&lt;/p&gt;

&lt;p&gt;The same pattern took over outside the journal too. Durable docs at work now carry a line like this one, from a July status page:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Point-in-time snapshot as of 2026-07-13; the live working copy is maintained in Claude Code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notion holds the snapshot a human wants to skim. The living version sits where the agent works. Six months ago I would have called that backwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I don't love
&lt;/h2&gt;

&lt;p&gt;Not everything the re-read turned up counts as progress.&lt;/p&gt;

&lt;p&gt;I read my own notes less than I used to. The machine's summaries are so complete that re-reading my fragments feels optional, and some days I skip it. But writing notes was never really about storage for me. It was the act that made me process the day. If the machine does the remembering and I skip the processing, I have automated the wrong half.&lt;/p&gt;

&lt;p&gt;Raw capture at speed is also indiscriminate. Reading back six months, I found stray GPS coordinates pasted alone on a page, screenshots with no caption, and at least one credential sitting in plain text where it had no business being. When the rule is "get it down now, sort it never," everything gets down, including things that should not.&lt;/p&gt;

&lt;p&gt;And the volume itself has not gotten smaller, only better organized. From my own July 28 entry, back from a conference, staring at a week of accumulated context:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Too much shallow work? Is that why we have so much brain frizz without any solid output?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Better notes did not fix that. They just made the frizz searchable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The system, as of today
&lt;/h2&gt;

&lt;p&gt;Here is what I actually do now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One page per day, created and maintained by a tool, not by discipline. Discipline did not survive contact with so many LLM sessions.&lt;/li&gt;
&lt;li&gt;The machine logs the sessions. Structured summaries with links, IDs, and follow-ups get appended automatically. I never paste transcripts anymore.&lt;/li&gt;
&lt;li&gt;My own notes are only fragments and questions, and that is their job. Cheap capture of what I was unsure about, in my own broken, probably misspelled, horrible grammer shorthand.&lt;/li&gt;
&lt;li&gt;Anything durable gets distilled out of the journal into a real artifact, a design doc, a ticket, a runbook, with a note about where the raw material lives.&lt;/li&gt;
&lt;li&gt;Resume pointers beat context re-explanation. A breadcrumb to a live session is worth more than three paragraphs describing where I left off.&lt;/li&gt;
&lt;li&gt;The last block of the day is for the first prompt of tomorrow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My notes got rewritten twice before I noticed, first into pasted transcripts, then into breadcrumbs pointing at where the real context lived. The third rewrite was mine. I still leave breadcrumbs, they just lead somewhere now, and reading back six months stopped feeling like scrolling and started feeling like remembering.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>notion</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Patch Your Rails: Active Storage CVE-2026-66066</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Wed, 29 Jul 2026 17:53:01 +0000</pubDate>
      <link>https://dev.to/cseeman/patch-now-active-storage-cve-2026-66066-53gp</link>
      <guid>https://dev.to/cseeman/patch-now-active-storage-cve-2026-66066-53gp</guid>
      <description>&lt;p&gt;Rails disclosed &lt;a href="https://discuss.rubyonrails.org/t/cve-2026-66066-possible-arbitrary-file-read-and-remote-code-execution-in-active-storage-variant-processing/91432" rel="noopener noreferrer"&gt;CVE-2026-66066&lt;/a&gt; this week: a possible arbitrary file read and remote code execution in Active Storage variant processing. If your app accepts image uploads and uses libvips, stop reading and go patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Am I Affected?
&lt;/h2&gt;

&lt;p&gt;You're affected if both of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app uses libvips for Active Storage (&lt;code&gt;config.active_storage.variant_processor = :vips&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Untrusted users can upload images that get variants generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Affected versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;activestorage &amp;lt; 7.2.3.2&lt;/li&gt;
&lt;li&gt;activestorage &amp;gt;= 8.0, &amp;lt; 8.0.5.1&lt;/li&gt;
&lt;li&gt;activestorage &amp;gt;= 8.1, &amp;lt; 8.1.3.1&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's the Risk?
&lt;/h2&gt;

&lt;p&gt;libvips marks some of its file format handlers as "unfuzzed operations," meaning they're not safe for untrusted content. Active Storage didn't disable them. So an attacker who uploads a crafted file and triggers variant generation can potentially read arbitrary files on the server, pull environment variables like &lt;code&gt;secret_key_base&lt;/code&gt;, and from there escalate to remote code execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Upgrade activestorage to 7.2.3.2, 8.0.5.1, or 8.1.3.1. The easy way is &lt;code&gt;bundle update rails --conservative&lt;/code&gt;, which bumps Rails to the patch release without updating anything else in your Gemfile.lock.&lt;/li&gt;
&lt;li&gt;Make sure libvips is &amp;gt;= 8.13.&lt;/li&gt;
&lt;li&gt;Rotate your secrets. The advisory is blunt about this: treat every secret readable by the application process as potentially exposed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third step is the one people skip. If your app was vulnerable, patching alone doesn't help you if someone already read your credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Can't Upgrade Yet
&lt;/h2&gt;

&lt;p&gt;There are workarounds, but only on libvips &amp;gt;= 8.13:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set the &lt;code&gt;VIPS_BLOCK_UNTRUSTED&lt;/code&gt; environment variable&lt;/li&gt;
&lt;li&gt;Or call &lt;code&gt;Vips.block_untrusted(true)&lt;/code&gt; from an initializer (requires ruby-vips &amp;gt;= 2.2.1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On libvips older than 8.13, there is no workaround. Upgrade.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The advisory: &lt;a href="https://discuss.rubyonrails.org/t/cve-2026-66066-possible-arbitrary-file-read-and-remote-code-execution-in-active-storage-variant-processing/91432" rel="noopener noreferrer"&gt;CVE-2026-66066&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>security</category>
      <category>activestorage</category>
    </item>
    <item>
      <title>Return on Attention: Why AI Code Reviews Are Wearing Us Out</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Thu, 09 Jul 2026 22:12:22 +0000</pubDate>
      <link>https://dev.to/cseeman/return-on-attention-why-ai-code-reviews-are-wearing-us-out-2hh0</link>
      <guid>https://dev.to/cseeman/return-on-attention-why-ai-code-reviews-are-wearing-us-out-2hh0</guid>
      <description>&lt;p&gt;Our team grew this year, and the PR volume has grown with it. Certainly faster than the ticket generation. New people means more code moving through the pipeline. It doesn't mean more context. A lot of mass PRs needed real thought just to read through: what problem this is solving, why this approach, what actually matters here versus what's incidental. That gap, more code, same amount of shared understanding, is where our troubles started.&lt;/p&gt;

&lt;p&gt;When reviews got harder and slower, people reached for the tool that could help them attempt to keep pace. We have an AI code review bot that runs as an automated reviewer on every PR now, and separately, plenty of us run different LLM and coding agents of choice locally to draft review comments. Put those two together and you get exactly what you'd expect: a bot commenting on a PR, another bot replying to it. Some of the LLM created reported bugs, were created in the first place with total confidence by a model that had no way to know it wasn't real. That is a real drawback about the AI coding assistance, confident wrongness.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it felt like
&lt;/h2&gt;

&lt;p&gt;Comments stopped sounding like the colleagues who supposedly wrote them. Verbose. Every possible reference and citation included, whether the moment called for it or not. We're a team that talks about code plainly, in our own words, and PRs started filling up with AI slop.&lt;/p&gt;

&lt;p&gt;One complaint came up more than once: having to leave the PR to get real context, pinging a person or a model outside the thread because the comment itself didn't actually contain enough, or just way, way too much filler. The entire point of a pull request comment is that you shouldn't have to go anywhere else for it.&lt;/p&gt;

&lt;p&gt;Another: duplication, and the cost of re-reading. A paragraph explaining code you could read directly in a minute doesn't save you time. It costs you time, on top of whatever the comment was supposed to save you from working out yourself.&lt;/p&gt;

&lt;p&gt;Someone on the team put it plainly: not sure what the right balance is now, but the whole paradigm has clearly shifted. Fair. What's less fair is assuming the way we're doing it right now is the only way it could be done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scarce resource
&lt;/h2&gt;

&lt;p&gt;A few weeks into this, our CEO said something in Slack that really got to the heart of the whole problem for me. The PR-review complaints were real, but they were a symptom, not the disease. His point: the actual bottleneck isn't PR review specifically, it's that the finite attention of skilled teammates can be easily overwhelmed by LLM-generated content. Human attention is the scarce resource worth guarding most zealously, and inflicting verbose, cheap-to-produce-but-expensive-to-consume writing on each other should be treated as a real anti-pattern. His term for it was Return-on-Attention, ROA: every word you ask someone else to read has to be worth what it costs them to read it.&lt;/p&gt;

&lt;p&gt;He backed it with two examples from that same week. Some ADRs had gone out with a lot of repetition in them, and a colleague had to read through all of it. Terrible ROA. A PR that gitignored a single directory, one line of code, ten characters, came with a description running 1,430 characters. Terrible ROA for whoever had to review it.&lt;/p&gt;

&lt;p&gt;Neither example is about code quality. Both are about what you're allowed to cost another person's attention to save yourself thirty seconds of editing.&lt;/p&gt;

&lt;p&gt;The same idea has a public version. &lt;a href="https://noslopgrenade.com/" rel="noopener noreferrer"&gt;noslopgrenade.com&lt;/a&gt; calls it a "slop grenade": pasting a massive AI-generated response into a chat or email where a human would have written one sentence. It's aimed at chat, not code review, but it's the identical failure. An LLM can produce more words than a task needs, for free, and the cost of that surplus doesn't disappear. It just moves to whoever has to read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I don't love admitting
&lt;/h2&gt;

&lt;p&gt;I noticed the same pull in myself, not as a reviewer but as a thinker. There were stretches where I'd hand a problem to the model before I'd actually sat with it, and I could feel my own workflow discipline getting looser. Not because the answers were wrong. Because I'd stopped doing the part where I figure things out first and ask questions second.&lt;/p&gt;

&lt;p&gt;I'm also mid-fight with a specific habit of Claude's: narrating design decisions into comments and code. "We decided this, not that." It shows up unprompted, and it's never once been useful to me. A decision like that belongs in a PR description or a commit message, where it has context and a date and an author. Sitting in a code comment, it's just noise that will be wrong the next time someone changes their mind and nobody remembers to delete it. I'm building a skill specifically to stop Claude from writing that pattern into a codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the line actually is
&lt;/h2&gt;

&lt;p&gt;Bot review isn't the problem. Misusing it is.&lt;/p&gt;

&lt;p&gt;It's useful when it gives a reviewer an angle they wouldn't have found on their own, or catches a bug, a missed edge case, a real issue sitting in the diff. That's a second pair of eyes, really helping.&lt;/p&gt;

&lt;p&gt;It stops being useful the moment it costs the reader more attention than it saves them. Review exists to ship value and to pass knowledge between people. Code quality matters because it serves that, not the other way around. If a comment leaves the code a little better but leaves the reviewer more drained, that's not a trade worth making.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attributed to you
&lt;/h2&gt;

&lt;p&gt;Here's a rule I use. When posting a review comment, it's attributed to you. Sure, a coding agent might be a co-author…but that isn't who's going to have to follow up. It isn't the one the PR author is going to come to with questions, or pushback. It's you. The PR author has no way to know how it got written. Sure they &lt;em&gt;probably&lt;/em&gt; know, especially if you're slapping em dashes all over the place. They're going to read it as your judgment, in your voice, and hold you to it exactly the way they would if you'd typed every word yourself.&lt;/p&gt;

&lt;p&gt;So that's the standard before you hit submit: would you have said this, at this length, in this tone? If the answer is no, the fix isn't a better prompt. It's an edit. Leave off what isn't you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codereview</category>
      <category>productivity</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>What Prime Day Taught Me About Prompt Engineering</title>
      <dc:creator>christine</dc:creator>
      <pubDate>Mon, 22 Jun 2026 18:26:49 +0000</pubDate>
      <link>https://dev.to/cseeman/what-prime-day-taught-me-about-prompt-engineering-3gek</link>
      <guid>https://dev.to/cseeman/what-prime-day-taught-me-about-prompt-engineering-3gek</guid>
      <description>&lt;p&gt;I wanted to get better at prompt engineering. Not the trick-the-robot kind, the boring-but-useful kind: how to ask a model a question so you get an answer you can actually trust.&lt;/p&gt;

&lt;p&gt;The trouble with practicing is that most tutorials use made-up examples, and it's hard to tell a good answer from a bad one when you don't care about the topic. So I practiced on something I did care about: the deals sitting in my Amazon cart. I had a vacuum I'd been eyeing and a hair styler that was "43% off," and I genuinely wanted to know if those were good prices or just good marketing. The stakes were real, actual money on an actual decision, and that's what made it a good drill. A vague prompt gives you a confident answer, and when you actually care, you can feel that the answer is hollow.&lt;/p&gt;

&lt;p&gt;What I learned, with the real deals and the actual before-and-after prompts:&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap hiding in every deal
&lt;/h2&gt;

&lt;p&gt;Start with the hair styler. The listing said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Shark FlexStyle. Limited time deal. $199.00, 43% savings. List Price: $349.99.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4xc8sbkdmavde1kqucj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4xc8sbkdmavde1kqucj1.png" alt="Amazon listing for the Shark FlexStyle showing $199.00 marked 43% off a $349.99 list price" width="544" height="1110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My first instinct was the prompt most people write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Shark FlexStyle $199, 43% off list $349.99, is that a good deal?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This feels reasonable. It is also nearly useless: it lets the model answer the &lt;em&gt;easy&lt;/em&gt; question (is 43% off a big discount? sure!) instead of the &lt;em&gt;real&lt;/em&gt; one (is $199 actually a good price?). That $349.99 list price is a marketing anchor. A lazy prompt accepts it, and so you get a lazy "yes, great deal!" back.&lt;/p&gt;

&lt;p&gt;The fix was re-framing this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Act as a pricing analyst. I don't care whether $199 looks like a discount off list. I care whether $199 is a genuinely good price for the Shark FlexStyle right now. Before concluding, work through: (1) the actual street price over the last 6-12 months, (2) how often it drops to or below $199, (3) the real discount vs. its typical selling price, not vs. list. Cite a source and date for each price, or mark it unverified.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same question, completely different answer. What the assistant came back with, in its own telling:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;$199 is a genuinely good price, but not for the reason Shark markets it. It's the 6-month floor and has only appeared a couple of times in that window. The more honest benchmark is its recent median price, and $199 is a real savings off &lt;em&gt;that&lt;/em&gt;, not the headline savings off the inflated $349.99 list.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpncjr9g07grvway1mnf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbpncjr9g07grvway1mnf.png" alt="The honest discount vs. street price is ~20% below median, and ~13% below its most common " width="800" height="1316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm taking those specifics on faith, the assistant reported them and I didn't independently verify its price history, but that's almost beside the point. What changed was the &lt;em&gt;shape&lt;/em&gt; of the answer: instead of "43% off, great deal!" I got "$199 is the all-time low on Amazon - only hit twice in 180 days, and the previous touch was one day only" The only thing I did differently was tell the model to ignore the anchor and compare to street price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson one: reframe the question to attack the anchor.&lt;/strong&gt; "Compare to street price, not list" is the line I am going to find more helpful going forward. It applies to nearly every "is this a good deal" question you'll ever ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  When "better" has no meaning yet
&lt;/h2&gt;

&lt;p&gt;Next I tried a comparison, which is where vague prompts fall apart:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dyson V8 Cordless: $269.99, 31% off list $389.99.&lt;/p&gt;

&lt;p&gt;Shark IZ363HT with HEPA filter: $199.99, 43% off list $349.99.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1101l5wds6urcygqy4z2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1101l5wds6urcygqy4z2.png" alt="Compare with similar" width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Just the plan old compare with similar on Alexa for Shopping on Amazon&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The trap is right there in the numbers. The Shark is 43% off and cheaper, so a lazy prompt crowns it the winner in one line. But "better deal" is meaningless until you say &lt;em&gt;better for what&lt;/em&gt;. A comparison has no answer until you define the criteria.&lt;/p&gt;

&lt;p&gt;The prompt I landed on added three new moves on top of the anchor break:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Act as a product and pricing analyst. Don't let a bigger discount percentage decide the winner. Compare the Dyson V8 ($269.99) and Shark IZ363HT ($199.99) as a purchase. Ignore list-price discounts; compare each to its real street price. Flag if they're different product classes. Score on weighted criteria: price-vs-street 30%, cleaning 25%, runtime 20%, HEPA 15%, upkeep 10%. Then answer two things separately: better DEAL, and better PRODUCT. Cite source and date per price or mark unverified.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Three things made this work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weighted criteria.&lt;/strong&gt; "Better" means nothing until you declare what matters and how much. Writing the weights forced me to admit what I actually cared about.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Split the deal from the product.&lt;/strong&gt; A thing can be a great deal &lt;em&gt;and&lt;/em&gt; the wrong buy. Keeping those two questions separate clears up most of the confusion in a comparison.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flag the class mismatch.&lt;/strong&gt; These two vacuums aren't the same tier, and I wanted the model to say so instead of comparing raw prices.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result split exactly the way good analysis should (again, these are the assistant's figures, which I didn't independently confirm):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Better DEAL: the Shark at $199.99, a genuine discount off its typical street price. The Dyson's deal is real but based on thin historical data. Better PRODUCT: the Dyson V8, for cleaner engineering and easier maintenance, though the Shark's HEPA seal is a meaningful win for a cat household.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zy6yfgi4o06c5yp19kc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zy6yfgi4o06c5yp19kc.png" width="800" height="1436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One prompt, two different winners. A single "which is better?" would have collapsed that into one arbitrary answer and I'd have learned nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson two: in any comparison, the quality of the answer is capped by whether you defined the criteria.&lt;/strong&gt; The model can't tell you "better" until you tell it "better how."&lt;/p&gt;

&lt;h2&gt;
  
  
  The model is smarter, not infallible
&lt;/h2&gt;

&lt;p&gt;The part that surprised me most came in that same comparison answer, which also contained this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The $130 gap vs. the Dyson is real money…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and later:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;…are comfortable paying $70 more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both can't be true. The actual gap is $269.99 minus $199.99, which is &lt;strong&gt;$70&lt;/strong&gt;. The $130 was wrong, and the model contradicted itself within the same answer. A well-engineered prompt made the reasoning much better. It did not make the arithmetic correct.&lt;/p&gt;

&lt;p&gt;The fix is one more constraint: &lt;em&gt;"show the price-difference math explicitly."&lt;/em&gt; Errors hide in summary claims but not in shown work. If I'd forced it to write &lt;code&gt;$269.99 - $199.99 = $70&lt;/code&gt;, the contradiction would have been impossible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson three: a better prompt improves reasoning, not truth. Always verify the model's math.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint I should've expected: word count
&lt;/h2&gt;

&lt;p&gt;When I copied that comparison prompt into the Amazon shopping assistant, only the first half pasted. There was a character limit, and my nicely-formatted prompt with its ROLE and TASK and OUTPUT headers got cut off mid-sentence.&lt;/p&gt;

&lt;p&gt;The annoying part is that nothing told me the limit existed until I hit it. These assistants rarely document it, so you find the ceiling by bumping your head on it. That changes the strategy: since you can't plan around a number you don't know, put the instructions that change the answer &lt;em&gt;first&lt;/em&gt;, so if you do get truncated, you lose the formatting and not the substance.&lt;/p&gt;

&lt;p&gt;That accident taught me something useful. Under a tight budget, you rank techniques by how much each word buys you and cut the rest. The headers and scaffolding? First to go, the model infers structure on its own. What you protect, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The anchor break (street price, not list)&lt;/li&gt;
&lt;li&gt;The deal-vs-product split&lt;/li&gt;
&lt;li&gt;The weighted criteria (compressed to inline shorthand)&lt;/li&gt;
&lt;li&gt;The cite-or-unverified rule&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The compressed version was a quarter the length and carried every essential instruction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Compare as a purchase: Dyson V8 $269.99 vs Shark IZ363HT (HEPA) $199.99. Ignore list-price discounts; compare each to its real street price. Note if they're different product classes. Score on: price-vs-street 30%, cleaning 25%, runtime 20%, HEPA 15%, upkeep 10%. Answer two things separately: better DEAL, and better PRODUCT. Cite source+date per price or mark unverified.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89ccxrjjt8ggc2s1g5c6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F89ccxrjjt8ggc2s1g5c6.png" alt="Shopping assistant's detailed reply with a price-vs-street table, 12-month price history chart, and a buy/wait verdict" width="800" height="1429"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Now with more details that you care about in the prompt. The engineered prompt, with the price-history table and chart it produced.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson four: a prompt's power lives in its constraints and reframes, not its formatting.&lt;/strong&gt; Headers are a readability luxury. The instructions that change the &lt;em&gt;answer&lt;/em&gt; are the ones you never cut.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same question, side by side
&lt;/h2&gt;

&lt;p&gt;This is the comparison I'd point a skeptic to. I ran a third product through both kinds of prompt so you can see the difference in one frame: the &lt;strong&gt;Garmin epix Pro (Gen 2) Sapphire Edition, 51mm&lt;/strong&gt;. A premium watch with a real list price north of $1,000, which makes the anchor problem even more tempting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without prompt engineering&lt;/strong&gt;, you write what feels natural:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is the Garmin epix Pro (Gen 2) Sapphire 51mm a good deal right now?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And you get back something that sounds authoritative and tells some but maybe not all you want:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjx4lwxlcyh59lqww6ue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxjx4lwxlcyh59lqww6ue.png" width="800" height="905"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The vague prompt, the vague(ish) answer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Notice what that answer does: it accepts the discount at face value, and uses list price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With prompt engineering&lt;/strong&gt;, you spend the reframes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Act as a pricing analyst. I care whether the current price of the Garmin epix Pro (Gen 2) Sapphire 51mm is genuinely good, not whether it looks discounted off list. Before concluding: (1) find its street/historical price over the last 6-12 months, (2) note how often it hits the current price or lower, (3) state the real discount vs. typical selling price, not list. Cite source and date per price or mark unverified. Then give a verdict: buy / wait / skip, and a "wait-for" target price if I should hold out.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;em&gt;shape&lt;/em&gt; of what comes back changes entirely. Instead of a sales pitch, you get a verdict with the reasoning shown:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwynfsk6qutgnem7dt51h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwynfsk6qutgnem7dt51h.png" width="800" height="1488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With some real, actionable information and price history in context:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pjsdyedxgekwvlgk55d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4pjsdyedxgekwvlgk55d.png" alt="12-month median: ~$624.90. The current $549.99 is a real ~$75" width="800" height="1410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is a decision, with the math shown and the uncertainty labeled. One answer is a brochure, the other tells you exactly how good the price is, how often it appears, and what to hold out for. The difference isn't the model. It's the four or five sentences of reframing you put in front of it.&lt;/p&gt;

&lt;p&gt;And one last thing, the part I didn't expect. The &lt;em&gt;first&lt;/em&gt; time I ran this, the assistant answered from only about 40 days of data and called it a ~$50 saving. The phrase "over the last 6-12 months" in my prompt is what made it go back, pull a full year of price history, and correct itself to the ~$75 figure with a proper wait-for target. The reframe didn't just change the format of the answer. It changed how hard the model looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cheat-sheet
&lt;/h2&gt;

&lt;p&gt;The whole thing distilled, for when you want to try this yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The five core techniques&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Role for clarity, not theater.&lt;/strong&gt; Set a role only when it changes what good output looks like, and say why. "Act as a pricing analyst; I care about real price history over marketing claims."&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Explicit output structure.&lt;/strong&gt; Tell it the shape: a table, then a verdict, then a confidence rating. Structure is the highest-leverage habit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Few-shot examples.&lt;/strong&gt; Show one example of the answer you want. It pins down tone and granularity better than any description.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reason before verdict.&lt;/strong&gt; Ask for the work first, conclusion last. Evidence-first ordering kills confident-but-wrong answers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Constraints, especially for uncertainty.&lt;/strong&gt; "If you can't verify it, say unverified rather than guessing." The uncertainty clause is the one people skip, and it's what prevents fabrication.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;For comparisons, add&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define and weight your criteria. No criteria, no real answer.&lt;/li&gt;
&lt;li&gt;Split "best deal" from "best product." Don't let a steeper discount pick the winner.&lt;/li&gt;
&lt;li&gt;Force the math to be shown so errors can't hide in summaries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For deals specifically&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Break the anchor: compare to street price, never to list price.&lt;/li&gt;
&lt;li&gt;Ask how &lt;em&gt;often&lt;/em&gt; the price appears, not just how low it is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Meta-habits&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specificity beats politeness. The model can't read intent you didn't state.&lt;/li&gt;
&lt;li&gt;Prompt engineering is debugging, not authorship. Run it, find the specific failure, fix that one thing.&lt;/li&gt;
&lt;li&gt;Watch which constraints the model quietly drops, then promote those to non-negotiable on the next pass.&lt;/li&gt;
&lt;li&gt;Under a word limit, cut formatting first and reframes last.&lt;/li&gt;
&lt;li&gt;Name the scope. A phrase like "over the last 6-12 months" makes the model look harder, not just format differently; it pulled a full year of data and corrected its own answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reading the patterns vs. using them
&lt;/h2&gt;

&lt;p&gt;None of this is new, and I knew that. I'd read &lt;a href="https://arxiv.org/abs/2302.11382" rel="noopener noreferrer"&gt;White et al. (2023), &lt;em&gt;A Prompt Pattern Catalog to Enhance Prompt Engineering with ChatGPT&lt;/em&gt;&lt;/a&gt; a while back. It catalogs prompt techniques as reusable "patterns," the way software design patterns work, and the moves I leaned on map almost one to one onto it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Act as a pricing analyst" is the &lt;strong&gt;Persona Pattern&lt;/strong&gt;: give the model a point of view so it knows what to focus on.&lt;/li&gt;
&lt;li&gt;"Compare to street price, not list" is the &lt;strong&gt;Question Refinement Pattern&lt;/strong&gt;: get the model to answer a better version of the question than the one you asked.&lt;/li&gt;
&lt;li&gt;"Work through (1), (2), (3) before concluding" is the &lt;strong&gt;Cognitive Verifier Pattern&lt;/strong&gt;: break a question into sub-questions and build the answer from them.&lt;/li&gt;
&lt;li&gt;"Cite each price or mark it unverified" and "verify the math" are the &lt;strong&gt;Fact Check List&lt;/strong&gt; and &lt;strong&gt;Reflection&lt;/strong&gt; patterns: make the model surface what its answer depends on, then check itself.&lt;/li&gt;
&lt;li&gt;The assistant offering to "pull price history on any specific item before you commit" is the &lt;strong&gt;Flipped Interaction Pattern&lt;/strong&gt;, where the model drives by asking you questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But knowing the catalog and using it are different skills. Reading the paper, the patterns felt like a tidy list to nod along to. It wasn't until I was staring at a $549 watch I actually wanted, with real money on the line, that "Question Refinement" stopped being a term and became a reflex: &lt;em&gt;no, don't accept the list price, make it compare to street.&lt;/em&gt; The patterns only stuck once I had a reason to care whether the answer was right.&lt;/p&gt;

&lt;p&gt;If you want an easier intro than the paper, Descript's &lt;a href="https://www.descript.com/blog/article/5-advanced-prompts-to-get-better-answers-from-chatgpt" rel="noopener noreferrer"&gt;5 Advanced Prompts to Get Better Answers from ChatGPT&lt;/a&gt; walks through five of these same patterns with plain examples. Read either one. Then go find a question you actually care about and use them, because that's the part the reading can't do for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual takeaway
&lt;/h2&gt;

&lt;p&gt;I set out to learn prompt engineering and ended up also learning that the Shark IZ363HT was a genuinely good deal and the Dyson was the better vacuum, which is its own kind of useful. Still don't know which to buy, but at least I do know more and have developed some better prompting habits. Every one of these techniques is just a way of refusing to accept the easy answer: don't trust the list price, don't accept an undefined "better," don't believe the math without seeing it.&lt;/p&gt;

&lt;p&gt;Pick something you actually want to know the answer to. That's the trick. When you care, you can feel the difference between a hollow answer and a real one, and chasing that difference is the entire skill.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>llm</category>
      <category>shopping</category>
    </item>
  </channel>
</rss>
