<?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: Semih ERDOGAN</title>
    <description>The latest articles on DEV Community by Semih ERDOGAN (@semiherdogan).</description>
    <link>https://dev.to/semiherdogan</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%2F3918052%2Fd806d51a-71a1-49fd-ab25-f6f49f46936e.jpeg</url>
      <title>DEV Community: Semih ERDOGAN</title>
      <link>https://dev.to/semiherdogan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/semiherdogan"/>
    <language>en</language>
    <item>
      <title>Context Rollovers Without /compact</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Wed, 08 Jul 2026 19:38:14 +0000</pubDate>
      <link>https://dev.to/semiherdogan/context-rollovers-without-compact-338l</link>
      <guid>https://dev.to/semiherdogan/context-rollovers-without-compact-338l</guid>
      <description>&lt;p&gt;Long AI coding sessions have a quiet failure mode.&lt;/p&gt;

&lt;p&gt;They do not always fail with a bad command, a broken test, or an obvious wrong turn.&lt;/p&gt;

&lt;p&gt;Sometimes they fail at the boundary between one context window and the next.&lt;/p&gt;

&lt;p&gt;That boundary is exactly where I do not like giving control to an opaque automatic compaction step.&lt;/p&gt;

&lt;p&gt;Most AI coding tools eventually hit the same problem. The conversation gets long, the context window gets full, and something needs to happen before the next step becomes unreliable.&lt;/p&gt;

&lt;p&gt;The default answer is usually some form of &lt;code&gt;/compact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I understand why it exists. A long transcript cannot grow forever.&lt;/p&gt;

&lt;p&gt;But I never liked the control model.&lt;/p&gt;

&lt;p&gt;The assistant decides what matters. It rewrites the conversation into a smaller representation. Then I continue from that summary, without really knowing what was dropped, what was compressed too aggressively, or which failed attempt is now missing from the working memory.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of hidden state I try to avoid in coding workflows.&lt;/p&gt;

&lt;p&gt;My first serious answer to this was &lt;a href="https://github.com/semiherdogan/handoff" rel="noopener noreferrer"&gt;&lt;code&gt;handoff&lt;/code&gt;&lt;/a&gt;, a local-first CLI for structured AI coding sessions.&lt;/p&gt;

&lt;p&gt;That still works well when I want a spec-driven flow: intent, spec, design, decisions, state, session summary, and drift checks.&lt;/p&gt;

&lt;p&gt;But not every workday task deserves that much process.&lt;/p&gt;

&lt;p&gt;Sometimes I am already deep in a repository, moving through small implementation steps, and I only need one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before the session gets cleared, write down exactly enough state for the next session to continue safely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I built a lighter setup around Claude Code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I wanted
&lt;/h2&gt;

&lt;p&gt;The goal was not to create another full workflow system.&lt;/p&gt;

&lt;p&gt;I wanted a small project-local rollover protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;disable automatic compaction&lt;/li&gt;
&lt;li&gt;show context usage while I work&lt;/li&gt;
&lt;li&gt;warn when context gets high&lt;/li&gt;
&lt;li&gt;write a concrete &lt;code&gt;HANDOFF.md&lt;/code&gt; before clearing&lt;/li&gt;
&lt;li&gt;load that handoff automatically in the fresh session&lt;/li&gt;
&lt;li&gt;keep everything inspectable as normal files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is that the handoff is explicit.&lt;/p&gt;

&lt;p&gt;I can open it, edit it, delete it, review it, or decide it is not good enough. The next session starts from a file I control, not from a hidden summary I have to trust.&lt;/p&gt;

&lt;p&gt;The project layout is intentionally small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.claude/
  settings.json
  skills/
    handoff/
      SKILL.md
  templates/
    HANDOFF.template.md
  scripts/
    context_monitor.py
    load_handoff.py
    statusline.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Claude settings
&lt;/h2&gt;

&lt;p&gt;The first decision is to turn off auto compaction.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"autoCompactEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CLAUDE_CONTEXT_WINDOW"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CLAUDE_ROLLOVER_WARN_BANDS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"75,85,92"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3 .claude/scripts/statusline.py"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"UserPromptSubmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3 &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$CLAUDE_PROJECT_DIR/.claude/scripts/context_monitor.py&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3 &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$CLAUDE_PROJECT_DIR/.claude/scripts/context_monitor.py&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"startup|clear"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3 &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$CLAUDE_PROJECT_DIR/.claude/scripts/load_handoff.py&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three moving parts here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;statusline.py&lt;/code&gt; keeps the current model, branch, and context usage visible while I work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;context_monitor.py&lt;/code&gt; runs after user prompts and tool use. When context usage crosses a configured band, it tells the session that rollover is recommended.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;load_handoff.py&lt;/code&gt; runs when a new session starts or after &lt;code&gt;/clear&lt;/code&gt;. If &lt;code&gt;HANDOFF.md&lt;/code&gt; exists at the project root, it injects it into the new session as additional context.&lt;/p&gt;

&lt;p&gt;That is the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context warning
&lt;/h2&gt;

&lt;p&gt;The monitor is deliberately boring.&lt;/p&gt;

&lt;p&gt;It reads the session transcript path from hook input, scans only the tail of the file, finds the latest main-chain assistant usage entry, and computes an approximate percentage against the context window.&lt;/p&gt;

&lt;p&gt;When the percentage crosses a band, it warns once for that band:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context at ~&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens). &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rollover recommended: run /handoff, then /clear.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[context-rollover] Context usage is at ~&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pct&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% of the window. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Per this project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s rollover protocol: finish the current atomic step &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(do not leave the working tree broken), then tell the user it is time &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to roll over and offer to run /handoff to write HANDOFF.md. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do NOT suggest /compact.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line matters.&lt;/p&gt;

&lt;p&gt;I do not want the assistant to solve this by falling back to the default compaction habit. The instruction is specific: finish the current atomic step, write a handoff, clear, resume.&lt;/p&gt;

&lt;p&gt;The hook also exits successfully on any internal error. A context monitor should never break the coding session.&lt;/p&gt;

&lt;h2&gt;
  
  
  The /handoff skill
&lt;/h2&gt;

&lt;p&gt;The user-facing command is a Claude skill called &lt;code&gt;handoff&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its job is to write one file: &lt;code&gt;HANDOFF.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The skill front matter keeps the tool surface tight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;handoff&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Write HANDOFF.md for a context rollover so the user can /clear and continue in a fresh session. Use when the context-rollover warning fires, when context usage is high, or when the user asks to hand off / roll over / continue in a new session.&lt;/span&gt;
&lt;span class="na"&gt;argument-hint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[optional&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;extra&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;notes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;carry&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;over]"&lt;/span&gt;
&lt;span class="na"&gt;allowed-tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Write, Bash(git status:*), Bash(git branch:*), Bash(git log:*), Bash(git diff:*), Bash(date:*)&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it captures live repository state at invocation time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Current repo state (captured at invocation):
&lt;span class="p"&gt;
-&lt;/span&gt; Date: !&lt;span class="sb"&gt;`date '+%Y-%m-%d %H:%M'`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Branch: !&lt;span class="sb"&gt;`git branch --show-current`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Working tree: !&lt;span class="sb"&gt;`git status --short`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Diff summary: !&lt;span class="sb"&gt;`git diff --stat HEAD`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Recent commits: !&lt;span class="sb"&gt;`git log --oneline -5`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the assistant hard facts before it writes the handoff.&lt;/p&gt;

&lt;p&gt;The rest of the skill is mostly constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;follow the template exactly&lt;/li&gt;
&lt;li&gt;use real paths and commands&lt;/li&gt;
&lt;li&gt;keep it dense&lt;/li&gt;
&lt;li&gt;include failed attempts&lt;/li&gt;
&lt;li&gt;make the next step immediately executable&lt;/li&gt;
&lt;li&gt;do not include secrets&lt;/li&gt;
&lt;li&gt;overwrite any existing &lt;code&gt;HANDOFF.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;do not commit anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The "failed attempts" section is the one I care about most.&lt;/p&gt;

&lt;p&gt;Successful work is usually visible in the diff. Failed attempts disappear unless someone writes them down. That is how fresh sessions waste time retrying commands, approaches, or assumptions that already failed twenty minutes ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  The template
&lt;/h2&gt;

&lt;p&gt;The template is short and intentionally operational:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Handoff&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; Generated: {date} - Branch: {branch} - Previous session: {one-line session label}&lt;/span&gt;

&lt;span class="gu"&gt;## Current Goal&lt;/span&gt;

&lt;span class="gu"&gt;## Current State&lt;/span&gt;

&lt;span class="gu"&gt;## Completed Work&lt;/span&gt;

&lt;span class="gu"&gt;## Key Decisions&lt;/span&gt;

&lt;span class="gu"&gt;## Files Changed&lt;/span&gt;

&lt;span class="gu"&gt;## Failed Attempts / Avoid&lt;/span&gt;

&lt;span class="gu"&gt;## Open Tasks&lt;/span&gt;

&lt;span class="gu"&gt;## Next Concrete Step&lt;/span&gt;

&lt;span class="gu"&gt;## User Preferences / Constraints&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a spec.&lt;/p&gt;

&lt;p&gt;It does not try to redesign the task. It does not ask for a product brief. It does not split the feature into phases.&lt;/p&gt;

&lt;p&gt;It answers one practical question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I clear the session right now, what does the next assistant need in order to continue without guessing?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The most important section is &lt;code&gt;Next Concrete Step&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not "continue the migration." Not "finish the API." Not "investigate tests."&lt;/p&gt;

&lt;p&gt;It should be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Open &lt;span class="sb"&gt;`app/services/billing/reconcile.py`&lt;/span&gt; and update &lt;span class="sb"&gt;`build_reconcile_query`&lt;/span&gt;
to include the new &lt;span class="sb"&gt;`invoice_status`&lt;/span&gt; predicate, then rerun
&lt;span class="sb"&gt;`pytest tests/services/test_billing_reconcile.py`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the difference between a useful handoff and a vague summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resuming after /clear
&lt;/h2&gt;

&lt;p&gt;The resume hook is just as small.&lt;/p&gt;

&lt;p&gt;On session start, it looks for &lt;code&gt;HANDOFF.md&lt;/code&gt; at the project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLAUDE_PROJECT_DIR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cwd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HANDOFF.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the file exists, it reads a bounded amount, includes the file age, and injects it as rollover context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;age_h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getmtime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_BYTES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[context-rollover] HANDOFF.md found at the project root &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(last modified &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age_h&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;h ago). It is the handoff from the previous &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session - resume from it. Read &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Next Concrete Step&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; and continue there. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Briefly confirm to the user that you resumed from HANDOFF.md and state &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the next step. If the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s first message is unrelated to this handoff, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;or it looks stale, say so and ask whether to delete HANDOFF.md.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;----- HANDOFF.md -----&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That stale-file behavior is important.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HANDOFF.md&lt;/code&gt; is useful when it is current. It is dangerous when it silently becomes old context. The hook does not delete or ignore old files by itself; it exposes the age and tells the fresh session to call out a stale or unrelated handoff instead of continuing blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual daily flow
&lt;/h2&gt;

&lt;p&gt;The day-to-day usage is simple.&lt;/p&gt;

&lt;p&gt;When the context warning appears, I do not immediately clear the session. First I let the assistant finish the current atomic step so the working tree is not left in a broken state.&lt;/p&gt;

&lt;p&gt;Then I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/handoff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That writes &lt;code&gt;HANDOFF.md&lt;/code&gt;. If the task is important, I quickly scan the file, especially &lt;code&gt;Failed Attempts / Avoid&lt;/code&gt; and &lt;code&gt;Next Concrete Step&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the fresh session, the &lt;code&gt;SessionStart&lt;/code&gt; hook loads &lt;code&gt;HANDOFF.md&lt;/code&gt;. I usually still send a small prompt such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Continue from HANDOFF.md.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prompt is not doing the loading. The hook already did that. It is just an explicit nudge that the fresh session should continue the rollover instead of treating the first turn as a new unrelated task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is separate from handoff
&lt;/h2&gt;

&lt;p&gt;My &lt;code&gt;handoff&lt;/code&gt; CLI and this Claude setup solve related but different problems.&lt;/p&gt;

&lt;p&gt;The CLI is for structured work. It is useful when I want the assistant to move from feature intent to spec, design, task state, decisions, evidence, and drift audit.&lt;/p&gt;

&lt;p&gt;This setup is for daily continuity.&lt;/p&gt;

&lt;p&gt;It does not ask me to work spec-first. It does not impose a planning phase. It only creates a controlled boundary between one session and the next.&lt;/p&gt;

&lt;p&gt;That makes it much easier to use during normal work.&lt;/p&gt;

&lt;p&gt;When the task is large or ambiguous, I still prefer the full &lt;code&gt;handoff&lt;/code&gt; workflow.&lt;/p&gt;

&lt;p&gt;When I am already coding and the context window is getting high, this lighter &lt;code&gt;/handoff&lt;/code&gt; is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Porting the idea to Codex or another agent
&lt;/h2&gt;

&lt;p&gt;The exact files will change by tool, but the design ports cleanly.&lt;/p&gt;

&lt;p&gt;You need four surfaces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;persistent instructions&lt;/li&gt;
&lt;li&gt;a reusable handoff command&lt;/li&gt;
&lt;li&gt;lifecycle hooks, if the tool supports them&lt;/li&gt;
&lt;li&gt;a project-local handoff file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For Claude Code, that became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.claude/settings.json
.claude/skills/handoff/SKILL.md
.claude/templates/HANDOFF.template.md
.claude/scripts/*.py
HANDOFF.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Codex, I would map the same idea differently.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;AGENTS.md&lt;/code&gt; for durable repository rules: when to roll over, what &lt;code&gt;HANDOFF.md&lt;/code&gt; means, and how fresh sessions should treat it.&lt;/p&gt;

&lt;p&gt;Use a Codex skill for the reusable workflow. Codex skills are plain directories with a &lt;code&gt;SKILL.md&lt;/code&gt;, and repo-scoped skills can live under &lt;code&gt;.agents/skills&lt;/code&gt;. A &lt;code&gt;handoff&lt;/code&gt; skill there can carry the same template discipline: inspect git state, write &lt;code&gt;HANDOFF.md&lt;/code&gt;, preserve failed attempts, and end with a concrete next step.&lt;/p&gt;

&lt;p&gt;Use Codex hooks for lifecycle glue. Codex supports hook events such as &lt;code&gt;UserPromptSubmit&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;SessionStart&lt;/code&gt;, &lt;code&gt;PreCompact&lt;/code&gt;, and &lt;code&gt;PostCompact&lt;/code&gt;, configured through &lt;code&gt;hooks.json&lt;/code&gt; or &lt;code&gt;config.toml&lt;/code&gt; under the relevant &lt;code&gt;.codex&lt;/code&gt; layer. That is enough to recreate the same pattern: warn before context rollover, discourage blind compaction, and load &lt;code&gt;HANDOFF.md&lt;/code&gt; when a fresh session starts.&lt;/p&gt;

&lt;p&gt;The shape would look 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;AGENTS.md
.agents/
  skills/
    handoff/
      SKILL.md
.codex/
  hooks.json
  hooks/
    context_monitor.py
    load_handoff.py
templates/
  HANDOFF.template.md
HANDOFF.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact hook payloads and statusline behavior are tool-specific, so I would not blindly copy the Claude scripts into Codex. I would keep the protocol and adapt the integration points.&lt;/p&gt;

&lt;p&gt;For tools without hooks, the fallback is still useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ask the assistant to write HANDOFF.md using the template.
Start a new session.
Paste or attach HANDOFF.md.
Tell the assistant to continue from Next Concrete Step.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is less automatic, but it keeps the most important property: the context boundary is explicit and reviewable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full example files
&lt;/h2&gt;

&lt;p&gt;I did not want to turn this post into a wall of Python, so I kept the full example files separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://semiherdogan.net/code/context-rollovers/settings.json" rel="noopener noreferrer"&gt;&lt;code&gt;settings.json&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semiherdogan.net/code/context-rollovers/handoff-skill.md" rel="noopener noreferrer"&gt;&lt;code&gt;handoff-skill.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semiherdogan.net/code/context-rollovers/HANDOFF.template.md" rel="noopener noreferrer"&gt;&lt;code&gt;HANDOFF.template.md&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semiherdogan.net/code/context-rollovers/context_monitor.py" rel="noopener noreferrer"&gt;&lt;code&gt;context_monitor.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semiherdogan.net/code/context-rollovers/load_handoff.py" rel="noopener noreferrer"&gt;&lt;code&gt;load_handoff.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://semiherdogan.net/code/context-rollovers/statusline.py" rel="noopener noreferrer"&gt;&lt;code&gt;statusline.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are meant as a starting point, not a package. The important part is the protocol: warn before rollover, write the handoff explicitly, clear the session, and load the handoff into the next one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I care about
&lt;/h2&gt;

&lt;p&gt;The real rule is simple:&lt;/p&gt;

&lt;p&gt;Do not let an invisible summary become the only bridge between two coding sessions.&lt;/p&gt;

&lt;p&gt;If the work matters, the bridge should be a file.&lt;/p&gt;

&lt;p&gt;It should say what changed, what failed, what decisions were made, what remains open, and what the next concrete step is.&lt;/p&gt;

&lt;p&gt;That does not need to be heavyweight.&lt;/p&gt;

&lt;p&gt;For big features, I still like structured specs and state files.&lt;/p&gt;

&lt;p&gt;For daily coding, a disciplined &lt;code&gt;HANDOFF.md&lt;/code&gt; is often enough.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>codex</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Agent Poke: Scheduled Check-ins for Codex and Claude Code</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Fri, 15 May 2026 09:14:03 +0000</pubDate>
      <link>https://dev.to/semiherdogan/agent-poke-scheduled-check-ins-for-codex-and-claude-code-44p6</link>
      <guid>https://dev.to/semiherdogan/agent-poke-scheduled-check-ins-for-codex-and-claude-code-44p6</guid>
      <description>&lt;p&gt;I use Codex CLI and Claude Code a lot during the day.&lt;/p&gt;

&lt;p&gt;Not through an API. Not through a custom integration.&lt;/p&gt;

&lt;p&gt;Mostly just the normal local CLIs, logged in with my subscription accounts, running inside whatever project I am working on.&lt;/p&gt;

&lt;p&gt;That workflow is simple enough, but there was one small thing I kept thinking about.&lt;/p&gt;

&lt;p&gt;Some of these tools meter usage in rolling time windows. In practice, the first message starts the window. If I only remember to open the tool later in the day, I may still have the same subscription, but the timing is not as useful as it could be.&lt;/p&gt;

&lt;p&gt;So I built a tiny helper for myself: &lt;code&gt;agent-poke&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/semiherdogan/agent-poke" rel="noopener noreferrer"&gt;github.com/semiherdogan/agent-poke&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It does not try to be smart.&lt;/p&gt;

&lt;p&gt;It just sends a small message to the agents at fixed times.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I wanted to solve
&lt;/h2&gt;

&lt;p&gt;Claude is most useful to me during the workday, roughly between &lt;code&gt;09:00&lt;/code&gt; and &lt;code&gt;19:00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Codex is useful almost all the time.&lt;/p&gt;

&lt;p&gt;At first, I thought about making the scheduler dynamic. It could track the last run, calculate the next allowed time, handle per-agent windows, and keep its own state.&lt;/p&gt;

&lt;p&gt;That was too much for this problem.&lt;/p&gt;

&lt;p&gt;What I actually needed was a static schedule that lines up with the day well enough.&lt;/p&gt;

&lt;p&gt;The current schedule is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 6 * * * /app/scripts/run-checkin.sh
6 11 * * * /app/scripts/run-checkin.sh
12 16 * * * /app/scripts/run-checkin.sh
18 21 * * * /app/scripts/run-checkin.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In plain English:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;06:00&lt;/code&gt; starts the first window before the workday&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;11:06&lt;/code&gt; is five hours plus six minutes later&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;16:12&lt;/code&gt; carries the window through the end of the workday&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;21:18&lt;/code&gt; opens an evening window with a little more buffer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The six extra minutes are intentional.&lt;/p&gt;

&lt;p&gt;I do not need to hit the boundary exactly. I only need to avoid firing slightly too early.&lt;/p&gt;

&lt;p&gt;The container timezone is configured through &lt;code&gt;TZ&lt;/code&gt; in &lt;code&gt;docker-compose.yml&lt;/code&gt;. For me that is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;TZ&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Europe/Istanbul&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Docker
&lt;/h2&gt;

&lt;p&gt;I want this to run on a server, not on my laptop.&lt;/p&gt;

&lt;p&gt;That made Docker Compose the simplest deployment target. The container has the two CLIs installed, a scheduler, and a small runner script.&lt;/p&gt;

&lt;p&gt;The shape is deliberately boring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent-poke/
  Dockerfile
  docker-compose.yml
  config/
    schedule.cron
  scripts/
    login-codex.sh
    login-claude.sh
    run-checkin.sh
  logs/
  workspace/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no database. No web UI. No queue. No service framework.&lt;/p&gt;

&lt;p&gt;It is just enough container around a few CLI commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Login is still manual
&lt;/h2&gt;

&lt;p&gt;This part was important to me.&lt;/p&gt;

&lt;p&gt;I did not want to bake credentials into an image or pretend subscription login is the same thing as an API key.&lt;/p&gt;

&lt;p&gt;The user logs in once, using the official CLI flow.&lt;/p&gt;

&lt;p&gt;For Codex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; agent-poke login-codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex login &lt;span class="nt"&gt;--device-auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; agent-poke login-claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude auth login &lt;span class="nt"&gt;--claudeai&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both CLIs save their login state under the container user's home directory.&lt;/p&gt;

&lt;p&gt;That directory is a Docker volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;agent_home:/home/agent&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the image can be rebuilt, but the login state stays.&lt;/p&gt;

&lt;p&gt;The one command to avoid is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That removes the volume, which also removes the saved logins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive check-ins
&lt;/h2&gt;

&lt;p&gt;My first thought was to avoid the interactive CLIs entirely.&lt;/p&gt;

&lt;p&gt;For scheduled jobs, non-interactive commands are usually cleaner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"Hey!"&lt;/span&gt;
claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Hey!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But for this specific tool, I decided against that.&lt;/p&gt;

&lt;p&gt;The goal is to behave like normal CLI usage. I do not want Codex and Claude to use different paths, and I do not want Claude to accidentally fall into a separate programmatic usage bucket if its metering changes.&lt;/p&gt;

&lt;p&gt;So the final version drives both CLIs the same way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open the interactive CLI with &lt;code&gt;Hey!&lt;/code&gt; as the initial prompt&lt;/li&gt;
&lt;li&gt;wait for the response to settle&lt;/li&gt;
&lt;li&gt;exit the CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The runner starts the agents in parallel. That matters because I do not want Claude to start several minutes after Codex just because one response is slow.&lt;/p&gt;

&lt;p&gt;Passing the message as the initial prompt also avoids a fragile timing problem: the scheduler does not have to wait for the TUI input box to become ready before typing.&lt;/p&gt;

&lt;p&gt;That means &lt;code&gt;expect&lt;/code&gt; is back in the image.&lt;/p&gt;

&lt;p&gt;It is a little less elegant than a pure non-interactive command, but it matches the way I actually use the tools.&lt;/p&gt;

&lt;p&gt;There is one operational consequence: after login, I run one manual check so both CLIs can ask and remember any workspace trust prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose run &lt;span class="nt"&gt;--rm&lt;/span&gt; agent-poke checkin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the scheduled runs use the same interactive path.&lt;/p&gt;

&lt;p&gt;The runner keeps recent logs only. The default is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;LOG_KEEP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough to inspect recent runs without letting a small helper slowly fill a disk.&lt;/p&gt;

&lt;p&gt;Raw terminal output is disabled by default because TUI tools emit a lot of escape sequences. If I need to debug the actual screen output, I can turn it back on with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;RAW_AGENT_OUTPUT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The normal log only records whether each agent started and completed successfully. I deliberately avoided parsing TUI output because small formatting changes in Codex or Claude could make that brittle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small deployment detail
&lt;/h2&gt;

&lt;p&gt;The container runs as a non-root user.&lt;/p&gt;

&lt;p&gt;That is usually the right default, but it means the host-mounted &lt;code&gt;logs/&lt;/code&gt; and &lt;code&gt;workspace/&lt;/code&gt; directories need to be writable by that user.&lt;/p&gt;

&lt;p&gt;On this image, the &lt;code&gt;agent&lt;/code&gt; user is &lt;code&gt;1001:1001&lt;/code&gt;, so the setup needs:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; logs workspace
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 1001:1001 logs workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That fixed the only real deployment issue I hit.&lt;/p&gt;

&lt;p&gt;Without it, the check-in script could start, but failed when it tried to create a log file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I like about it
&lt;/h2&gt;

&lt;p&gt;This is one of those tools where the main feature is that there are not many features.&lt;/p&gt;

&lt;p&gt;The final version is just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fixed schedule&lt;/li&gt;
&lt;li&gt;manual login&lt;/li&gt;
&lt;li&gt;persistent home volume&lt;/li&gt;
&lt;li&gt;interactive CLI check-ins&lt;/li&gt;
&lt;li&gt;plain log files&lt;/li&gt;
&lt;li&gt;Docker Compose deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough for now.&lt;/p&gt;

&lt;p&gt;If I ever need per-agent scheduling or smarter state, I can add it later.&lt;/p&gt;

&lt;p&gt;But for this problem, the static version is easier to trust.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codex</category>
      <category>claude</category>
      <category>docker</category>
    </item>
    <item>
      <title>handoff: Keeping AI Coding Sessions on Track</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Fri, 08 May 2026 05:55:05 +0000</pubDate>
      <link>https://dev.to/semiherdogan/handoff-keeping-ai-coding-sessions-on-track-op0</link>
      <guid>https://dev.to/semiherdogan/handoff-keeping-ai-coding-sessions-on-track-op0</guid>
      <description>&lt;p&gt;AI coding tools are very good at helping with the next step.&lt;/p&gt;

&lt;p&gt;They are much worse at reliably carrying the full thread of a feature across multiple sessions.&lt;/p&gt;

&lt;p&gt;That was the first problem I kept running into.&lt;/p&gt;

&lt;p&gt;I would start a feature with Claude, ChatGPT, Copilot, or another assistant, make real progress, then step away. When I came back, I had the same questions again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What exactly was I building?&lt;/li&gt;
&lt;li&gt;What decisions had already been made?&lt;/li&gt;
&lt;li&gt;What was the current step?&lt;/li&gt;
&lt;li&gt;What should happen next?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model had no memory. I had partial memory. The repo had some memory. None of it was structured enough.&lt;/p&gt;

&lt;p&gt;But continuity was only part of the issue.&lt;/p&gt;

&lt;p&gt;The deeper problem was that intent, requirements, decisions, current progress, and validation evidence were scattered across chat history and code changes.&lt;/p&gt;

&lt;p&gt;So I built &lt;code&gt;handoff&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What handoff is
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; is a local-first CLI for structured AI coding workflows.&lt;/p&gt;

&lt;p&gt;It creates a small workspace inside your repository under &lt;code&gt;.handoff/&lt;/code&gt; and uses plain Markdown files as the source of truth for the feature you are working on.&lt;/p&gt;

&lt;p&gt;The core workflow now revolves around six files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FEATURE.md&lt;/code&gt;: raw feature intent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SPEC.md&lt;/code&gt;: normalized requirements and acceptance criteria&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DESIGN.md&lt;/code&gt;: optional technical design&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DECISIONS.md&lt;/code&gt;: durable product and architecture decisions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STATE.md&lt;/code&gt;: execution plan, progress, and evidence&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SESSION.md&lt;/code&gt;: continuation-safe session summary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No cloud sync. No provider lock-in. No hidden agent runtime.&lt;/p&gt;

&lt;p&gt;Just files, prompts, and a deterministic workflow from intent to execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem was not only memory
&lt;/h2&gt;

&lt;p&gt;At first, I thought continuation was the only missing piece.&lt;/p&gt;

&lt;p&gt;After enough AI-assisted coding sessions, I realized there were actually several different problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the assistant forgets where the work stopped&lt;/li&gt;
&lt;li&gt;the assistant starts implementation before the work is decomposed well enough&lt;/li&gt;
&lt;li&gt;important decisions lose their reasoning&lt;/li&gt;
&lt;li&gt;completed work lacks evidence&lt;/li&gt;
&lt;li&gt;implementation quietly drifts away from the spec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those later problems matter just as much.&lt;/p&gt;

&lt;p&gt;If the feature is vague, the assistant tends to improvise. Sometimes that works. Sometimes it creates drift, partial implementation, or too much code too early.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;handoff&lt;/code&gt; is intent-aware, planning-aware, decision-aware, and continuation-aware.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow I wanted
&lt;/h2&gt;

&lt;p&gt;I wanted something with the strengths of structured decomposition, but without forcing people into one IDE or one proprietary workflow.&lt;/p&gt;

&lt;p&gt;I also did not want a system where users had to manually juggle several tools just to start a feature.&lt;/p&gt;

&lt;p&gt;So the result became a hybrid workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple when you want speed&lt;/li&gt;
&lt;li&gt;explicit when you want control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The default path
&lt;/h2&gt;

&lt;p&gt;For most features, the flow starts like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff init payment-integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you edit &lt;code&gt;.handoff/current/FEATURE.md&lt;/code&gt; with the feature request, requirements, and constraints.&lt;/p&gt;

&lt;p&gt;After that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff run &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is now the default entry point.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;handoff run&lt;/code&gt; looks at the saved workspace state and decides what prompt should come next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if planning is incomplete, it emits a planning prompt&lt;/li&gt;
&lt;li&gt;if the execution plan is ready, it emits an execution prompt&lt;/li&gt;
&lt;li&gt;if execution is already underway, it emits a continuation prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That keeps the simple path simple.&lt;/p&gt;

&lt;p&gt;You can also use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff next
handoff status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to inspect what the tool thinks should happen next without generating another prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The advanced path
&lt;/h2&gt;

&lt;p&gt;Sometimes you want to review the planning before any code is written.&lt;/p&gt;

&lt;p&gt;For that, &lt;code&gt;handoff&lt;/code&gt; also has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff spec &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff design &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff tasks &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those commands let you inspect the work in stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spec&lt;/code&gt;: turn feature intent into clear requirements&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;design&lt;/code&gt;: map those requirements to a practical implementation approach&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tasks&lt;/code&gt;: generate an execution-ready task list in &lt;code&gt;STATE.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff start &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and begin implementation from a cleaner plan.&lt;/p&gt;

&lt;p&gt;That distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;run&lt;/code&gt; is the default state-aware entry point&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;start&lt;/code&gt; is for direct execution when a valid plan already exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a closing-loop command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff drift &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;handoff drift&lt;/code&gt; does not modify code. It generates a structured audit prompt that asks an assistant to compare the saved intent, spec, design, decision log, state, session summary, and implementation.&lt;/p&gt;

&lt;p&gt;That gives you a final check before you call the feature done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the file split matters
&lt;/h2&gt;

&lt;p&gt;The value is not just "more files."&lt;/p&gt;

&lt;p&gt;The value is that each file has one job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FEATURE.md&lt;/code&gt; captures intent&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SPEC.md&lt;/code&gt; captures what must be true&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DESIGN.md&lt;/code&gt; captures how to approach it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DECISIONS.md&lt;/code&gt; captures why durable choices were made&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STATE.md&lt;/code&gt; captures what is being done right now&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;STATE.md&lt;/code&gt; also captures evidence for completed steps&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SESSION.md&lt;/code&gt; captures what the next session must know&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That separation gives the assistant better footing.&lt;/p&gt;

&lt;p&gt;It also gives you better reviewability. You can inspect the spec before implementation, challenge the design before code, review decisions later, and check whether the task list actually matches the feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decisions are part of the memory
&lt;/h2&gt;

&lt;p&gt;Requirements are not the only thing worth preserving.&lt;/p&gt;

&lt;p&gt;In long-running work, the more expensive loss is often decision history:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why did we choose this approach?&lt;/li&gt;
&lt;li&gt;What alternatives did we reject?&lt;/li&gt;
&lt;li&gt;Is this decision still valid?&lt;/li&gt;
&lt;li&gt;Should a future assistant re-open this topic?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why new feature workspaces include &lt;code&gt;DECISIONS.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is intentionally lightweight. It is not for every small implementation detail. It is for durable product or architecture choices that future sessions should not re-litigate without new evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence changes the execution loop
&lt;/h2&gt;

&lt;p&gt;AI-assisted coding often ends with a vague claim that work is done.&lt;/p&gt;

&lt;p&gt;That is not enough.&lt;/p&gt;

&lt;p&gt;The default execution prompts now ask the assistant to record evidence in &lt;code&gt;STATE.md&lt;/code&gt; after completed micro-steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;changed files&lt;/li&gt;
&lt;li&gt;commands or tests run&lt;/li&gt;
&lt;li&gt;result&lt;/li&gt;
&lt;li&gt;notes or remaining risks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That turns the loop from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task -&amp;gt; "done"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task -&amp;gt; code -&amp;gt; evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is still simple Markdown, but it gives you something concrete to review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift audit before closing
&lt;/h2&gt;

&lt;p&gt;The last failure mode is silent drift.&lt;/p&gt;

&lt;p&gt;A feature can have a good spec, a reasonable plan, and passing tests while still missing part of the original intent.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;handoff drift --copy&lt;/code&gt; exists for that moment.&lt;/p&gt;

&lt;p&gt;It emits a prompt for an audit, not an automatic verdict. That distinction is important. The CLI stays deterministic and provider-agnostic; the assistant does the code inspection using the saved artifacts as the checklist.&lt;/p&gt;

&lt;p&gt;The goal is to catch mismatches like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a requirement in &lt;code&gt;SPEC.md&lt;/code&gt; that never reached implementation&lt;/li&gt;
&lt;li&gt;an accepted decision in &lt;code&gt;DECISIONS.md&lt;/code&gt; that code ignored&lt;/li&gt;
&lt;li&gt;a completed task in &lt;code&gt;STATE.md&lt;/code&gt; without matching evidence&lt;/li&gt;
&lt;li&gt;a session summary that no longer reflects the repo&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why determinism matters
&lt;/h2&gt;

&lt;p&gt;One of the design goals of &lt;code&gt;handoff&lt;/code&gt; is determinism.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;handoff continue&lt;/code&gt; is guarded.&lt;/p&gt;

&lt;p&gt;If the execution plan is invalid, the command fails with a deterministic error instead of pretending everything is fine.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no execution plan initialized&lt;/li&gt;
&lt;li&gt;multiple current &lt;code&gt;[&amp;gt;]&lt;/code&gt; steps&lt;/li&gt;
&lt;li&gt;no remaining steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That behavior is deliberate.&lt;/p&gt;

&lt;p&gt;I do not want a workflow that silently fixes state by guessing. I want the handoff to be inspectable and stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Imagine I am adding a payment flow.&lt;/p&gt;

&lt;p&gt;I might start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff init payment-flow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;FEATURE.md&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;support Stripe checkout&lt;/li&gt;
&lt;li&gt;keep existing order flow intact&lt;/li&gt;
&lt;li&gt;show clear errors&lt;/li&gt;
&lt;li&gt;do not refactor unrelated modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From there I have two options.&lt;/p&gt;

&lt;p&gt;Fast path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff run &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reviewable path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff spec &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff design &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff tasks &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff start &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once work is in motion, I can continue with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next session gets a prompt grounded in the existing state, not a vague memory of yesterday.&lt;/p&gt;

&lt;p&gt;Before closing the work, I can ask for a drift audit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff drift &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prompt checks whether the implementation still matches the saved intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better continuity is not only about prompts
&lt;/h2&gt;

&lt;p&gt;One thing I like about the current shape of &lt;code&gt;handoff&lt;/code&gt; is that it is not only a prompt generator anymore.&lt;/p&gt;

&lt;p&gt;It also helps surface state, decisions, evidence, and drift.&lt;/p&gt;

&lt;p&gt;That is why commands like these matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;handoff status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handoff next&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handoff validate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They make the saved workflow visible instead of burying it inside one long prompt.&lt;/p&gt;

&lt;p&gt;That is important when you are trying to answer simple questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this feature ready for execution?&lt;/li&gt;
&lt;li&gt;What is the current step?&lt;/li&gt;
&lt;li&gt;Why is the workflow blocked?&lt;/li&gt;
&lt;li&gt;What evidence exists for completed work?&lt;/li&gt;
&lt;li&gt;Did implementation drift from the spec or decisions?&lt;/li&gt;
&lt;li&gt;What command should I run next?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why local-first still matters
&lt;/h2&gt;

&lt;p&gt;I wanted this to work with any coding assistant.&lt;/p&gt;

&lt;p&gt;That meant the core could not depend on one provider, one editor, or one hosted workflow system.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;handoff&lt;/code&gt; stays local-first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown files in your repository&lt;/li&gt;
&lt;li&gt;prompt generation from a CLI&lt;/li&gt;
&lt;li&gt;no provider dependency in the core flow&lt;/li&gt;
&lt;li&gt;no cloud requirement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use it with ChatGPT today, Claude tomorrow, Copilot later, or another tool entirely.&lt;/p&gt;

&lt;p&gt;The workflow stays yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why repository context matters too
&lt;/h2&gt;

&lt;p&gt;Feature state is only part of the story.&lt;/p&gt;

&lt;p&gt;Repository context matters too.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;README.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt; are missing, stale, or too thin, AI sessions still waste time rediscovering the same project facts.&lt;/p&gt;

&lt;p&gt;That is why &lt;code&gt;handoff init&lt;/code&gt; can flag missing high-value context, and why there is also a &lt;code&gt;handoff prompt context&lt;/code&gt; flow for improving repo-level guidance without writing application code.&lt;/p&gt;

&lt;p&gt;That may sound small, but it matters in practice.&lt;/p&gt;

&lt;p&gt;A feature plan works much better when the surrounding repository is legible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I still like the CLI model
&lt;/h2&gt;

&lt;p&gt;There is a lot of value in editor-native workflows, and I may support more of them over time.&lt;/p&gt;

&lt;p&gt;But the CLI model has one major strength: portability.&lt;/p&gt;

&lt;p&gt;The moment a workflow depends too heavily on one IDE, it becomes harder to reuse across tools, harder to debug, and harder to trust.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; keeps the source of truth in files you can inspect directly.&lt;/p&gt;

&lt;p&gt;That makes it easier to reason about, easier to version, and easier to carry across environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; is a good fit if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you build features across multiple AI sessions&lt;/li&gt;
&lt;li&gt;you switch between assistants or editors&lt;/li&gt;
&lt;li&gt;you want better task decomposition before coding&lt;/li&gt;
&lt;li&gt;you want decision history outside chat&lt;/li&gt;
&lt;li&gt;you want evidence attached to completed work&lt;/li&gt;
&lt;li&gt;you want drift checks before closing features&lt;/li&gt;
&lt;li&gt;you care about deterministic state and inspectable workflow&lt;/li&gt;
&lt;li&gt;you prefer local tools over hosted orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is probably not for you if you want a full IDE platform with built-in visual workflow management and heavy automation everywhere.&lt;/p&gt;

&lt;p&gt;That is fine. The tool is intentionally narrower than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;handoff&lt;/code&gt; gives AI coding workflows intent, memory, decisions, evidence, and a deterministic continuation path without taking ownership of your editor or your repository.&lt;/p&gt;

&lt;p&gt;That is the whole idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to try the current default flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff init my-feature
&lt;span class="c"&gt;# edit .handoff/current/FEATURE.md&lt;/span&gt;
handoff run &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff next
handoff status
handoff drift &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to inspect planning in stages first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;handoff init my-feature
&lt;span class="c"&gt;# edit .handoff/current/FEATURE.md&lt;/span&gt;
handoff spec &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff design &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff tasks &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff start &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="nt"&gt;--copy&lt;/span&gt;
handoff drift &lt;span class="nt"&gt;--copy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Project link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/semiherdogan/handoff" rel="noopener noreferrer"&gt;handoff on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are building with AI every day, you already know the pain this solves.&lt;/p&gt;

&lt;p&gt;The interesting part is not that the model needs context.&lt;/p&gt;

&lt;p&gt;It is that context needs structure.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cli</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Cloudflare Tunnel, Traefik, and a Cleaner Self-Hosted Setup</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Thu, 07 May 2026 13:26:01 +0000</pubDate>
      <link>https://dev.to/semiherdogan/cloudflare-tunnel-traefik-and-a-cleaner-self-hosted-setup-546l</link>
      <guid>https://dev.to/semiherdogan/cloudflare-tunnel-traefik-and-a-cleaner-self-hosted-setup-546l</guid>
      <description>&lt;p&gt;Today I set up a small stack on my own server using Docker, Cloudflare Tunnel, and Traefik.&lt;/p&gt;

&lt;p&gt;The goal was simple: I wanted a cleaner way to expose and manage multiple projects without treating every new deployment like a one-off configuration job.&lt;/p&gt;

&lt;p&gt;The useful part was not just that it worked. It made the server feel like a reusable system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wanted this
&lt;/h2&gt;

&lt;p&gt;I like keeping small projects available online, but once there is more than one service involved, setup work starts repeating itself very quickly.&lt;/p&gt;

&lt;p&gt;You need routing, domain handling, HTTPS, container management, and some way to stop the server from becoming a pile of unrelated manual decisions.&lt;/p&gt;

&lt;p&gt;I wanted something that felt structured enough to grow, but still lightweight enough for personal projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Docker runs the services&lt;/li&gt;
&lt;li&gt;Traefik handles reverse proxying and routing&lt;/li&gt;
&lt;li&gt;Cloudflare Tunnel connects the server to the outside world without the usual direct exposure model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker gives each project a predictable runtime shape. Traefik gives those projects a consistent entry point. Cloudflare Tunnel removes a lot of the usual friction around exposing services safely.&lt;/p&gt;

&lt;p&gt;What makes the stack work is that the responsibilities are separated in a sensible way. Containers are responsible for running applications. Traefik is responsible for routing and service discovery. The tunnel is responsible for controlled external access. Once those boundaries are clear, the setup becomes easier to extend without each layer leaking too much into the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just keep it simpler?
&lt;/h2&gt;

&lt;p&gt;That is a fair question, because for one app, a simpler setup is often good enough. A straightforward reverse proxy config is not a problem when there is only one destination behind it.&lt;/p&gt;

&lt;p&gt;The problem starts when the server stops being "the place where one app runs" and becomes "the place where different projects may come and go over time."&lt;/p&gt;

&lt;p&gt;That is the point where I stop caring only about whether something works and start caring more about whether the structure will still feel reasonable after the fourth or fifth service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this starts to make sense
&lt;/h2&gt;

&lt;p&gt;With a single app, almost any setup can feel acceptable. A manual proxy config does not look too bad when there is only one target. But once there are multiple services, different hostnames, and the possibility of adding more over time, the difference becomes obvious.&lt;/p&gt;

&lt;p&gt;That is the point where Traefik stops feeling optional. It becomes the layer that gives the server a stable structure. Services become easier to reason about, routing stops feeling ad hoc, and adding another project starts to look like a repeatable step instead of a fresh configuration job.&lt;/p&gt;

&lt;p&gt;The most useful part was reduction in repeated decisions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;services run in containers&lt;/li&gt;
&lt;li&gt;routing is handled in one consistent way&lt;/li&gt;
&lt;li&gt;external access follows the same pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding another project no longer looks like "what do I need to invent for this one?" It looks more like "how does this fit into the structure that already exists?"&lt;/p&gt;

&lt;p&gt;That is a much better place to be, especially for side projects and internal tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  What adding a new project looks like
&lt;/h2&gt;

&lt;p&gt;At a high level, adding a new project comes down to two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;add a hostname rule in the Cloudflare Tunnel dashboard&lt;/li&gt;
&lt;li&gt;add Traefik labels to the project's &lt;code&gt;docker-compose.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the Cloudflare side, the rule is straightforward: point something like &lt;code&gt;test.semiherdogan.net&lt;/code&gt; to the Traefik entry point behind the tunnel.&lt;/p&gt;

&lt;p&gt;On the container side, the project can stay very small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.enable=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.test.rule=Host(`test.semiherdogan.net`)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.routers.test.entrypoints=web&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik.http.services.test.loadbalancer.server.port=80&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The required shape is easy to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;join the shared proxy network&lt;/li&gt;
&lt;li&gt;declare the hostname in Traefik&lt;/li&gt;
&lt;li&gt;let the tunnel rule send traffic to that entry point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That repeatability matters more than the individual labels themselves. Once the naming and network pattern are stable, the cost of adding another service drops a lot because the shape of the problem is already known.&lt;/p&gt;

&lt;p&gt;Cloudflare Tunnel also changes the order of concerns in a useful way. Instead of thinking first about direct exposure, port handling, and the outer edge of the server, I can focus more on internal service organization and routing.&lt;/p&gt;

&lt;p&gt;That does not remove the need to think carefully, but it does remove some of the repetitive edge-work that usually makes self-hosting feel more fragile than it needs to be.&lt;/p&gt;

&lt;p&gt;It also gives me an easy path for the cases where a domain, subdomain, or internal endpoint should not be openly reachable. In those cases, Cloudflare Zero Trust fits naturally into the same setup and makes it straightforward to put a protected layer in front of something without rethinking the whole deployment model.&lt;/p&gt;

&lt;p&gt;It is easier to extend, easier to keep mentally organized, and much nicer to work with than a collection of separate, hand-made deployment paths.&lt;/p&gt;

&lt;p&gt;The interesting part is not the individual tools. It is that together they create a setup where future projects feel cheaper to add.&lt;/p&gt;

&lt;p&gt;And that is exactly the kind of infrastructure decision I tend to like: not flashy, but immediately useful once the number of projects starts growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I would be careful
&lt;/h2&gt;

&lt;p&gt;The main risk in setups like this is not usually the first deployment. It is slow operational drift.&lt;/p&gt;

&lt;p&gt;If hostnames, labels, networks, and entry points are not kept consistent, the "clean structure" benefit disappears surprisingly fast. The same is true if observability is left behind. Once multiple services share the same routing layer, it becomes more important to know what is failing, where requests are going, and which layer is actually responsible when something breaks.&lt;/p&gt;

&lt;p&gt;That is also why I like this stack more as a pattern than as a one-time setup. The value is not just that the first app is online. The value is that the second, third, and fourth app can follow the same rules without the server turning back into improvisation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is probably too much
&lt;/h2&gt;

&lt;p&gt;If you only have one small app and do not expect that to change, this may be more structure than you actually need. In that case, a simpler setup might be the better engineering decision.&lt;/p&gt;

&lt;p&gt;What makes this stack appealing is not that it is universally better. It is that it starts paying off when you want a server to host multiple projects without every new service feeling like a fresh round of setup decisions.&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>docker</category>
      <category>selfhosted</category>
      <category>devops</category>
    </item>
    <item>
      <title>Devbox: The First Nix-Based Tool That Felt Practical</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Thu, 07 May 2026 13:25:51 +0000</pubDate>
      <link>https://dev.to/semiherdogan/devbox-the-first-nix-based-tool-that-felt-practical-4mbb</link>
      <guid>https://dev.to/semiherdogan/devbox-the-first-nix-based-tool-that-felt-practical-4mbb</guid>
      <description>&lt;p&gt;Recently I started using &lt;a href="https://github.com/jetify-com/devbox" rel="noopener noreferrer"&gt;Devbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It is one of those tools that made immediate sense to me because it sits exactly in the gap I have been feeling for a long time.&lt;/p&gt;

&lt;p&gt;Before that, my default workflow for trying a new idea was usually one of two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install whatever I needed directly on my machine with Homebrew&lt;/li&gt;
&lt;li&gt;put the whole thing into Docker, even when the project was small&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both approaches work, but neither one felt right all the time.&lt;/p&gt;

&lt;p&gt;Installing tools directly on my laptop is easy in the short term, but it slowly turns the machine into a place where random project decisions accumulate. A tool I needed once ends up sitting around forever. Version differences start to matter. The system becomes less intentional over time.&lt;/p&gt;

&lt;p&gt;Docker solves a different problem, and sometimes it is exactly the right answer. But for very small experiments, side projects, or quick idea testing, it can also feel heavier than what I actually need. Sometimes I do not want a containerized runtime model. I just want a clean development shell with the right tools available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I never fully committed to Nix
&lt;/h2&gt;

&lt;p&gt;I have wanted something like this for a long time, which is why Nix has always been interesting to me in theory.&lt;/p&gt;

&lt;p&gt;The promise is very attractive: reproducible environments, clean dependency boundaries, and less pollution on the host machine.&lt;/p&gt;

&lt;p&gt;The part that always stopped me was not the idea. It was the feeling that the configuration itself asked for more commitment than I wanted to give, especially for small projects. Every time I looked at a Nix-based setup directly, it felt like I needed to buy into a whole way of thinking before I could get the practical benefit I was actually after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Devbox clicked
&lt;/h2&gt;

&lt;p&gt;Devbox was the first tool in this space that felt like it reduced the activation energy enough for me to actually use it.&lt;/p&gt;

&lt;p&gt;The part I liked immediately is that it gives me the Nix-backed isolation I wanted, but through a much more approachable shape. Instead of feeling like I need to adopt the Nix language first, I can describe a project environment in a small JSON file and move on.&lt;/p&gt;

&lt;p&gt;Instead of thinking, "do I want to invest in learning this whole toolchain right now?", the question becomes much simpler: "do I want this project to have a clean shell with pinned tools?"&lt;/p&gt;

&lt;p&gt;That is a much easier yes.&lt;/p&gt;

&lt;p&gt;Jetify's own docs describe Devbox as a way to create isolated, reproducible development shells without needing Docker or the Nix language, and that matches the part that mattered most to me in practice. Sources: &lt;a href="https://www.jetify.com/devbox/docs" rel="noopener noreferrer"&gt;What is Devbox?&lt;/a&gt;, &lt;a href="https://github.com/jetify-com/devbox" rel="noopener noreferrer"&gt;Devbox GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;p&gt;When I want to try a small project, I do not have to decide between making my laptop messier or creating more container setup than the project deserves. I can define the tools, enter the shell, and keep going.&lt;/p&gt;

&lt;p&gt;The flow is simple enough that it is easy to remember:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;devbox init
devbox search hugo
devbox add hugo@0.159.0

devbox shell &lt;span class="c"&gt;# activate environment&lt;/span&gt;
hugo server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the config stays small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hugo@0.159.0"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hugo server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"hugo --gc --minify"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is what made it click for me. It gives me the boundary I wanted without forcing a heavier setup than the project deserves.&lt;/p&gt;

&lt;p&gt;It also makes cleanup feel more realistic. When the project is over, I do not need to remember which packages I threw onto my machine just to get through one afternoon of testing. The dependency boundary stays with the project.&lt;/p&gt;

&lt;p&gt;That is the part I appreciate most. It feels lighter than Docker for this category of work, but still much more intentional than installing everything globally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it feels worth keeping
&lt;/h2&gt;

&lt;p&gt;Some tools are impressive but never become habit.&lt;/p&gt;

&lt;p&gt;Devbox feels more promising to me because it fits into the kind of development work I actually do: small experiments, side projects, trying tools quickly, switching contexts often, and not wanting my machine to reflect every temporary decision I make.&lt;/p&gt;

&lt;p&gt;I still think Docker and plain host installs both have their place. This is not really about replacing everything with one tool.&lt;/p&gt;

&lt;p&gt;It is more that Devbox finally gave me a practical middle layer I had been missing for a long time.&lt;/p&gt;

&lt;p&gt;That is why it clicked.&lt;/p&gt;

</description>
      <category>devbox</category>
      <category>nix</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Simplifying EC2 Connections with AWS SSH</title>
      <dc:creator>Semih ERDOGAN</dc:creator>
      <pubDate>Thu, 07 May 2026 13:25:39 +0000</pubDate>
      <link>https://dev.to/semiherdogan/simplifying-ec2-connections-with-aws-ssh-370o</link>
      <guid>https://dev.to/semiherdogan/simplifying-ec2-connections-with-aws-ssh-370o</guid>
      <description>&lt;p&gt;As a developer working with AWS, I often found myself jumping between multiple EC2 instances. Connecting to them securely and efficiently was repetitive, and it usually meant bouncing between the AWS CLI and the Session Manager Plugin.&lt;/p&gt;

&lt;p&gt;Those tools are powerful, but I wanted something simpler and faster for day-to-day use. That is why I built AWS SSH, a CLI tool with a small TUI that streamlines the process of connecting to EC2 instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built AWS SSH
&lt;/h2&gt;

&lt;p&gt;The AWS CLI and Session Manager Plugin already solve the core problem, but the workflow can still feel clumsy if you switch between instances often.&lt;/p&gt;

&lt;p&gt;The main friction points for me were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually searching for instance IDs and typing long commands.&lt;/li&gt;
&lt;li&gt;Having no intuitive interface for picking the target instance.&lt;/li&gt;
&lt;li&gt;Needing extra steps whenever I wanted to switch regions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted a tool that reduced that overhead and made the flow interactive instead of procedural.&lt;/p&gt;

&lt;h2&gt;
  
  
  How AWS SSH works
&lt;/h2&gt;

&lt;p&gt;AWS SSH is a wrapper around the AWS CLI and Session Manager Plugin. It uses those existing tools and puts a more ergonomic interface on top.&lt;/p&gt;

&lt;p&gt;The basic flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It fetches the EC2 instances available in your AWS account.&lt;/li&gt;
&lt;li&gt;It shows them in a TUI so you can search and select the one you want.&lt;/li&gt;
&lt;li&gt;Once selected, it starts the connection through AWS Session Manager.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It also supports region configuration. If you define regions in your AWS credentials file, the tool can search them automatically. If not, it asks you to pick a region at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before using AWS SSH, make sure these are installed and available in your &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" rel="noopener noreferrer"&gt;Session Manager plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can verify both with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--version&lt;/span&gt;
session-manager-plugin &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Download the latest release from the &lt;a href="https://github.com/semiherdogan/aws-ssh/releases" rel="noopener noreferrer"&gt;GitHub releases page&lt;/a&gt;, make it executable, and move it into your &lt;code&gt;PATH&lt;/code&gt;.&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;chmod&lt;/span&gt; +x aws-ssh
&lt;span class="nb"&gt;mv &lt;/span&gt;aws-ssh /usr/local/bin/aws-ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS, the first run may require allowing the binary from &lt;strong&gt;System Settings &amp;gt; Privacy &amp;amp; Security&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Run the tool like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws-ssh &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--profile&lt;/span&gt;|-p] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--region&lt;/span&gt;|-r] searchparam1 searchparam2 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That opens the interface, lets you filter the instance list, and then connects to the selected EC2 instance through AWS Session Manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A simple TUI for selecting EC2 instances.&lt;/li&gt;
&lt;li&gt;Search support for narrowing down instances quickly.&lt;/li&gt;
&lt;li&gt;Region configuration through your AWS credentials file.&lt;/li&gt;
&lt;li&gt;Optional profile selection with &lt;code&gt;--profile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuring regions
&lt;/h2&gt;

&lt;p&gt;If you want the tool to search specific regions automatically, add a &lt;code&gt;regions&lt;/code&gt; key to the relevant section in your &lt;code&gt;.aws/credentials&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[default]&lt;/span&gt;
&lt;span class="py"&gt;aws_access_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;YOUR_ACCESS_KEY&lt;/span&gt;
&lt;span class="py"&gt;aws_secret_access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;YOUR_SECRET_KEY&lt;/span&gt;
&lt;span class="py"&gt;regions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;us-east-1,us-west-2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, AWS SSH will search those regions without asking each time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Required AWS permissions
&lt;/h2&gt;

&lt;p&gt;To run the tool, you need at least these IAM permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ec2:DescribeInstances&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ssm:StartSession&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example policy looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"ssm:StartSession"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want tighter access control, you can scope &lt;code&gt;Resource&lt;/code&gt; down to specific instances or tags.&lt;/p&gt;

&lt;p&gt;Also make sure SSM is enabled on the target EC2 instance, with the required IAM role attached and the SSM agent installed and running. AWS documents that setup here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-prerequisites.html" rel="noopener noreferrer"&gt;Session Manager prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this tool is useful
&lt;/h2&gt;

&lt;p&gt;AWS SSH is meant to remove friction from a workflow that developers repeat constantly. If you manage one instance it is convenient. If you manage many, it becomes much more valuable.&lt;/p&gt;

&lt;p&gt;Project links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/semiherdogan/aws-ssh" rel="noopener noreferrer"&gt;AWS SSH repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;AWS CLI installation guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" rel="noopener noreferrer"&gt;Session Manager plugin installation guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>ec2</category>
      <category>cli</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
