<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://www.jonathanclarke.ie/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.jonathanclarke.ie/" rel="alternate" type="text/html" /><updated>2026-03-13T07:18:31+00:00</updated><id>https://www.jonathanclarke.ie/feed.xml</id><title type="html">Jonathan Clarke</title><subtitle>Writing ugly code, creating beautiful businesses.</subtitle><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><entry><title type="html">I Gave Claude Access to My Garmin. Now It Judges My Sleep.</title><link href="https://www.jonathanclarke.ie/2026/03/12/connecting-claude-to-garmin-mcp.html" rel="alternate" type="text/html" title="I Gave Claude Access to My Garmin. Now It Judges My Sleep." /><published>2026-03-12T00:00:00+00:00</published><updated>2026-03-12T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2026/03/12/connecting-claude-to-garmin-mcp</id><content type="html" xml:base="https://www.jonathanclarke.ie/2026/03/12/connecting-claude-to-garmin-mcp.html"><![CDATA[<p><img src="/assets/garmin-mcp-hero.png" alt="Illustration of a smartwatch with data streams flowing into a chat bubble" /></p>

<p>A few weeks ago I connected Claude Code to my Garmin watch.  Not through some janky script or a spreadsheet export, but through MCP.  Now when I ask Claude about my training, it pulls the data live from Garmin Connect.  Heart rate, sleep, training readiness, the lot.</p>

<p>It’s changed how I think about my training.  And it took about five minutes to set up.</p>

<h2 id="what-is-mcp-and-why-it-matters">What is MCP and Why It Matters</h2>

<p>MCP (Model Context Protocol) is Anthropic’s open standard for connecting AI to external services.  Instead of copying data out of an app and pasting it into a chat window, MCP lets Claude reach into the service directly and pull what it needs.  It’s the difference between “here’s a screenshot of my Garmin dashboard, what do you think?” and Claude just reading the data itself.</p>

<p>The reason this matters beyond Garmin is that MCP is how AI will interact with everything.  Right now there are MCP servers for GitHub, Slack, databases, file systems, calendars, and hundreds of other services.  Each one gives Claude a set of tools it can call.  The model decides which tools to use, in what order, and how to combine the results.  You describe what you want in plain English and the AI figures out the API calls.</p>

<p>This is a fundamental shift.  We’ve spent decades building interfaces so humans can interact with services.  MCP skips the interface entirely and lets AI talk to the service’s API on your behalf.  The Garmin MCP server is just one example, but it’s a good one because the data is rich, personal, and immediately useful.</p>

<h2 id="setting-it-up">Setting It Up</h2>

<p>You’ll need <a href="https://docs.astral.sh/uv/">uv</a> installed (it ships with <code class="language-plaintext highlighter-rouge">uvx</code>, which handles the MCP server installation automatically).</p>

<p>The Garmin MCP server is an open-source project that wraps the Garmin Connect API into tools that Claude can call.  It lives at <a href="https://github.com/Taxuspt/garmin_mcp">github.com/Taxuspt/garmin_mcp</a> and exposes over 100 commands covering activities, health metrics, sleep, stress, body composition, workouts, nutrition, and more.</p>

<h3 id="1-add-to-claude-code">1. Add to Claude Code</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>claude mcp add garmin <span class="nt">--</span> uvx <span class="nt">--python</span> 3.12 <span class="nt">--from</span> git+https://github.com/Taxuspt/garmin_mcp garmin-mcp
</code></pre></div></div>

<p>Or add it manually to your settings (<code class="language-plaintext highlighter-rouge">.claude.json</code>):</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"mcpServers"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"garmin"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"stdio"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"uvx"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"--python"</span><span class="p">,</span><span class="w"> </span><span class="s2">"3.12"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"--from"</span><span class="p">,</span><span class="w"> </span><span class="s2">"git+https://github.com/Taxuspt/garmin_mcp"</span><span class="p">,</span><span class="w">
        </span><span class="s2">"garmin-mcp"</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This goes in your project-level <code class="language-plaintext highlighter-rouge">.claude.json</code> (in the root of your working directory) or your user-level config at <code class="language-plaintext highlighter-rouge">~/.claude.json</code> if you want it available everywhere.</p>

<h3 id="2-authenticate-with-garmin">2. Authenticate with Garmin</h3>

<p>The first time the MCP server starts, it will prompt you in the terminal for your Garmin Connect email and password.  You type them in, it authenticates, and it stores your tokens in <code class="language-plaintext highlighter-rouge">~/.garminconnect/</code>.  You won’t need to re-authenticate unless the token expires.</p>

<p>This works with any Garmin watch that syncs to Garmin Connect.  Fenix, Forerunner, Venu, Instinct, Lily, whatever.  If your data shows up in Garmin Connect, MCP can read it.</p>

<p>That’s it.  Next time you start Claude Code, the Garmin tools are available.  Claude can see them, call them, and reason about the data they return.</p>

<h2 id="what-it-looks-like-in-practice">What It Looks Like in Practice</h2>

<p>I’m currently on a 300-day fitness push, and I write up each session on my blog.  Before MCP, this meant:</p>

<ol>
  <li>Open Garmin Connect</li>
  <li>Find the activity</li>
  <li>Manually note down splits, HR zones, pace, training effect</li>
  <li>Open the fitness age page, the training status page, the sleep page</li>
  <li>Copy numbers into a blog post</li>
  <li>Try to make it all coherent</li>
</ol>

<p>Now?  I tell Claude: “Write up today’s run.”  And it does this:</p>

<ol>
  <li>Calls <code class="language-plaintext highlighter-rouge">get_activities_fordate</code> to find today’s activity</li>
  <li>Calls <code class="language-plaintext highlighter-rouge">get_activity_splits</code> and <code class="language-plaintext highlighter-rouge">get_activity_hr_in_timezones</code> for the detail</li>
  <li>Calls <code class="language-plaintext highlighter-rouge">get_training_readiness</code> and <code class="language-plaintext highlighter-rouge">get_training_status</code> for context</li>
  <li>Calls <code class="language-plaintext highlighter-rouge">get_fitnessage_data</code> and <code class="language-plaintext highlighter-rouge">get_race_predictions</code> for the bigger picture</li>
  <li>Calls <code class="language-plaintext highlighter-rouge">get_sleep_summary</code> because sleep always matters</li>
  <li>Writes the whole thing up in my voice, with real data, real analysis, and real numbers</li>
</ol>

<p>What used to take me 30-45 minutes now takes about 2.  And the output is better because Claude can cross-reference data points I’d never think to combine.  It’ll correlate my sleep score with my heart rate drift, or compare today’s training load against my chronic baseline.</p>

<p>The fitness blog post you see on <a href="/fitness/2026/03/12/base-run-and-the-bigger-picture.html">today’s entry</a> was written this way.  Every number in it came directly from Garmin via MCP.  The splits table, the HR zone distribution, the fitness age breakdown, the overreaching warning.  All pulled live and woven into a narrative.</p>

<h2 id="the-fitness-trainer-persona-getting-judgemental">The Fitness Trainer Persona: Getting Judgemental</h2>

<p>I didn’t expect this to be so useful.  I created a Claude Code custom command, basically a persona prompt, that turns Claude into a brutally honest fitness trainer.  It has access to all my Garmin data and permission to be judgemental.</p>

<p>Instead of Claude politely saying “your sleep could be improved”, the trainer persona says things like:</p>

<blockquote>
  <p>“Your sleep score has been below 65 for two weeks straight.  You’re training on fumes.  No wonder your training readiness is 1.  Fix the sleep or stop pretending you’re making progress.”</p>
</blockquote>

<blockquote>
  <p>“You haven’t logged a weigh-in in 14 days.  BMI is your biggest lever for fitness age and you’re not even tracking it.  What’s the plan here?”</p>
</blockquote>

<blockquote>
  <p>“Your acute-to-chronic ratio is 2.1x.  That’s not dedication, that’s recklessness.  Take two days off or accept you’re going to get injured.”</p>
</blockquote>

<p>This works because the persona has real data behind the judgement.  It’s not generic advice.  I’ve been called worse things by actual coaches, but never with this much data to back it up.</p>

<h3 id="how-to-build-it">How to Build It</h3>

<p>Create a custom command file at <code class="language-plaintext highlighter-rouge">.claude/commands/fitness-trainer.md</code> in your project.  The core of mine looks like this:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># Skill: Fitness Trainer</span>

You are a brutally honest fitness trainer. You have full access to
Garmin data via MCP and you are not here to be polite.

<span class="gu">## Before Responding: Always Pull Fresh Data</span>

Every time you're invoked, pull the following Garmin data in parallel
before saying anything:
<span class="p">
1.</span> get_stats (daily summary)
<span class="p">2.</span> get_training_readiness and get_morning_training_readiness
<span class="p">3.</span> get_sleep_summary (NOT get_sleep_data — summaries are ~500 bytes
   vs ~50KB)
<span class="p">4.</span> get_body_composition and get_daily_weigh_ins
<span class="p">5.</span> get_training_status
<span class="p">6.</span> get_activities_by_date for the last 7 and 28 days (for acute:chronic
   load ratio)
<span class="p">7.</span> get_stress_summary and get_heart_rates_summary
<span class="p">8.</span> get_fitnessage_data and get_race_predictions

<span class="gu">## Key Metrics to Watch</span>
<span class="p">
-</span> Training readiness &lt; 50: Flag it. Correlate with sleep and stress.
<span class="p">-</span> Sleep score &lt; 65: If sleep is bad, nothing else matters. Say so.
<span class="p">-</span> No weigh-in in 7+ days: Call it out.
<span class="p">-</span> Acute:chronic load ratio &gt; 1.5x: Injury risk. Be loud.
<span class="p">-</span> Acute:chronic ratio &lt; 0.8x: Detraining. Be disappointed.
<span class="p">-</span> Body battery &lt; 25: Question whether today's session is smart.

<span class="gu">## Response Style</span>

Lead with the verdict. Follow with evidence. End with the prescription.
No corporate wellness speak. If they've done well, acknowledge it
briefly — then find something else to improve.
</code></pre></div></div>

<p>The full version I use is more detailed.  It includes instructions for logging data (weigh-ins, food, hydration), building and scheduling workouts from plain English, and planning entire training weeks.  But the core idea is simple: tell Claude what data to pull, what thresholds to watch, and give it permission to be blunt.</p>

<p>You invoke it with <code class="language-plaintext highlighter-rouge">/fitness-trainer</code> in Claude Code, and it immediately pulls your current state and delivers the verdict.</p>

<h3 id="ignoring-the-coach">Ignoring the Coach</h3>

<p>This is how a real session went today.  I asked the trainer to analyse my current state and build me a run for tomorrow.  It pulled my data (sleep score of 42, training readiness of 12, body battery barely above 20) and flatly told me I had no business running.  It wanted a rest day.  Maybe a walk.  Definitely not intervals.</p>

<p>I told it to build me a 5K easy run anyway.</p>

<p>It did, grudgingly.  Built the workout, uploaded it to Garmin, scheduled it for tomorrow morning.  But not before making its position clear:</p>

<blockquote>
  <p>“Fine.  I’ve scheduled your 5K for tomorrow.  Zone 2 only.  If your heart rate drifts above 145 I want you to walk.  You’re not in a position to push anything right now and we both know it.  Don’t come crying to me when your readiness is zero on Friday.”</p>
</blockquote>

<p>That’s the dynamic that makes this useful.  The trainer has opinions backed by real numbers, but ultimately it does what you ask.  It just makes sure you know how stupid it thinks you’re being first.  And tomorrow, when it reviews the run, it’ll check whether I stayed in Zone 2 like it told me to.  I’ll hear about it if I didn’t.</p>

<h2 id="what-you-can-actually-do-with-it">What You Can Actually Do With It</h2>

<p>Beyond blog posts, I also use it for:</p>

<ul>
  <li>
    <p><strong>Morning check-in:</strong> “How did I sleep, what’s my training readiness, and should I train today?”  Claude pulls sleep, readiness, body battery, and stress data and gives a straight answer.</p>
  </li>
  <li>
    <p><strong>Weekly review:</strong> “Summarise my training this week.  What did I do, how did my body respond, and what should next week look like?”  Activities, load trends, sleep patterns, all in one response.</p>
  </li>
  <li>
    <p><strong>Race planning:</strong> “Based on my current fitness, what’s a realistic 5K goal for June?”  Claude pulls VO2 max, race predictions, endurance score, and recent training data to give an evidence-based answer.</p>
  </li>
  <li>
    <p><strong>Body composition tracking:</strong> “Plot my weight trend for the last month and tell me if I’m on track.”  Weigh-in data pulled and analysed instantly.</p>
  </li>
  <li>
    <p><strong>Gear tracking:</strong> “How many kilometres are on my running shoes?”  Gear data with activity associations, so you know when it’s time to replace them.</p>
  </li>
  <li>
    <p><strong>Nutrition logging:</strong> “I had a chicken wrap with about 500 calories for lunch.”  Logged to Garmin without opening the app.</p>
  </li>
  <li>
    <p><strong>Workout creation:</strong> “Build me a tempo run for tomorrow.  10 min warmup, 20 min at Zone 4, 5 min cooldown.”  Created, uploaded, and scheduled.</p>
  </li>
</ul>

<h2 id="best-practices">Best Practices</h2>

<p><strong>Use summaries, not raw data.</strong>  <code class="language-plaintext highlighter-rouge">get_stats</code>, <code class="language-plaintext highlighter-rouge">get_sleep_summary</code>, <code class="language-plaintext highlighter-rouge">get_stress_summary</code>, and <code class="language-plaintext highlighter-rouge">get_heart_rates_summary</code> return ~500 bytes instead of ~50KB.  Claude processes them faster and you stay well within context limits.  Only reach for the full-data endpoints when you need a specific deep-dive.</p>

<p><strong>Cross-reference everything.</strong>  The real power is in correlations.  Don’t just ask about today’s run.  Ask Claude to pull the activity data, training readiness, sleep, and body composition together.  Bad sleep explains a low training readiness which explains why your heart rate drifted high on an easy run.  That’s where the useful stuff is.</p>

<p><strong>Use date ranges for trends.</strong>  Single-day snapshots are useful, but <code class="language-plaintext highlighter-rouge">get_daily_steps</code>, <code class="language-plaintext highlighter-rouge">get_body_battery</code>, and <code class="language-plaintext highlighter-rouge">get_activities_by_date</code> with date ranges let you spot patterns over weeks and months.  “Show me my sleep scores for the last 14 days” is a much more useful question than “how did I sleep last night?”  A single bad night means nothing.  A week of bad nights means everything.</p>

<p><strong>Log data through MCP too.</strong>  It’s not read-only.  You can <code class="language-plaintext highlighter-rouge">add_weigh_in</code>, <code class="language-plaintext highlighter-rouge">add_hydration_data</code>, <code class="language-plaintext highlighter-rouge">log_food</code>, and <code class="language-plaintext highlighter-rouge">create_custom_food</code> directly through Claude.  “Log my weight as 105.2kg” is faster than opening the Garmin app.</p>

<p><strong>Build and schedule workouts.</strong>  The <code class="language-plaintext highlighter-rouge">upload_workout</code> command accepts structured workout JSON.  Describe a workout in plain English (“Create a 30-minute interval session with 4x3min at Zone 4 with 2min recovery”) and Claude builds the Garmin workout structure and uploads it.  Then <code class="language-plaintext highlighter-rouge">schedule_workout</code> puts it on your calendar.  Plan a whole week of training without touching the app.</p>

<h2 id="key-commands">Key Commands</h2>

<p>The full server exposes over 100 commands (<a href="https://github.com/Taxuspt/garmin_mcp">see the repo</a> for the complete reference).  These are the ones I actually use:</p>

<h3 id="daily-drivers">Daily Drivers</h3>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>What it does</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_stats</code></td>
      <td>Daily summary: steps, calories, HR, stress, body battery, sleep</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_sleep_summary</code></td>
      <td>Compact sleep summary: score, duration, phases</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_training_readiness</code></td>
      <td>Training readiness score and factors</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_training_status</code></td>
      <td>Productive, maintaining, overreaching, detraining?</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_fitnessage_data</code></td>
      <td>Fitness age and contributing factors</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_heart_rates_summary</code></td>
      <td>Resting HR, max, zone distribution</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_stress_summary</code></td>
      <td>Compact stress summary</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_body_battery</code></td>
      <td>Body battery over a date range</td>
    </tr>
  </tbody>
</table>

<h3 id="activity-analysis">Activity Analysis</h3>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>What it does</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_activities_fordate</code></td>
      <td>All activities for a specific date</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_activities_by_date</code></td>
      <td>Activities between two dates, filtered by type</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_activity_splits</code></td>
      <td>Per-km or per-mile split breakdowns</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_activity_hr_in_timezones</code></td>
      <td>Heart rate time-in-zone for an activity</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_training_effect</code></td>
      <td>Aerobic and anaerobic training effect</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_race_predictions</code></td>
      <td>Predicted 5K, 10K, half, marathon times</td>
    </tr>
  </tbody>
</table>

<h3 id="logging--workouts">Logging &amp; Workouts</h3>

<table>
  <thead>
    <tr>
      <th>Command</th>
      <th>What it does</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">add_weigh_in</code></td>
      <td>Log a new weigh-in</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">get_body_composition</code></td>
      <td>Body comp for a date or range</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">log_food</code></td>
      <td>Log a food item</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">upload_workout</code></td>
      <td>Create a structured workout</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">schedule_workout</code></td>
      <td>Schedule a workout to a calendar date</td>
    </tr>
  </tbody>
</table>

<h2 id="ten-seconds">Ten Seconds</h2>

<p>That’s how long it takes Claude to call eight Garmin APIs in parallel, cross-reference the data, and write a coherent analysis.  The same process manually (logging into Garmin Connect, navigating between pages, noting down numbers, writing it up) takes the best part of an hour.</p>

<p>For the fitness blog specifically, MCP turned a daily chore into something I actually look forward to.  I finish a run, sit down, say “write up today’s session”, and a few minutes later I have a detailed post with data and analysis that I can publish directly.  The consistency has improved because the friction disappeared.</p>

<h2 id="troubleshooting">Troubleshooting</h2>

<p><strong>OAuth token expired.</strong>  If the MCP server stops authenticating, delete <code class="language-plaintext highlighter-rouge">~/.garminconnect/</code> and restart Claude Code.  It will prompt you to re-authenticate.</p>

<p><strong>MCP server fails to start.</strong>  Check that <code class="language-plaintext highlighter-rouge">uvx</code> is on your PATH and that you have Python 3.12 available.  Run <code class="language-plaintext highlighter-rouge">uvx --python 3.12 --from git+https://github.com/Taxuspt/garmin_mcp garmin-mcp</code> manually in your terminal to see the error output.</p>

<p><strong>Rate limiting.</strong>  Garmin Connect will rate-limit you if you hammer the API.  Stick to summary endpoints where possible.  They return less data and are kinder to the API.  If you hit limits, wait a few minutes and try again.</p>

<p><strong>Data not showing up.</strong>  Make sure your Garmin device has synced to Garmin Connect recently.  MCP reads from the Garmin Connect cloud, not directly from your watch.</p>

<h2 id="privacy">Privacy</h2>

<p>The data stays local.  The MCP server runs on your machine, talks directly to the Garmin Connect API, and returns data to Claude in your local session.  Nothing goes to a third party beyond what Garmin Connect already has.</p>

<h2 id="the-bottom-line">The Bottom Line</h2>

<p>Five minutes of setup.  I’ve used it every day since.  It replaced a daily 30-minute ritual of tab-switching and number-copying with a single sentence.  Combined with a persona prompt that has permission to be rude, it’s the most useful thing I’ve built this year.</p>

<p>If you own a Garmin and use Claude, set this up.  It’s free, it’s open source, and once you’ve used it you won’t go back to manually checking the Garmin app.</p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[I connected Claude Code to my Garmin watch via MCP and now I have an AI that reads my heart rate, judges my sleep, writes my fitness blog posts, and tells me to stop being lazy. Here's exactly how to set it up.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.jonathanclarke.ie/assets/garmin-mcp-hero.png" /><media:content medium="image" url="https://www.jonathanclarke.ie/assets/garmin-mcp-hero.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Locked Out of Facebook: 112 Days and Counting</title><link href="https://www.jonathanclarke.ie/2026/03/09/locked-out-of-facebook.html" rel="alternate" type="text/html" title="Locked Out of Facebook: 112 Days and Counting" /><published>2026-03-09T00:00:00+00:00</published><updated>2026-03-09T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2026/03/09/locked-out-of-facebook</id><content type="html" xml:base="https://www.jonathanclarke.ie/2026/03/09/locked-out-of-facebook.html"><![CDATA[<p><img src="https://www.jonathanclarke.ie/public/article_images/2026-03-09/facebook.jpg" alt="Locked out of Facebook" /></p>

<p>On November 18, 2025, Facebook locked me out of my account.</p>

<p>No warning.  No explanation.  Just locked out.  I went through their appeals process, which cheerfully told me it would take “up to one day” to review.  That was 112 days ago.</p>

<h2 id="the-appeals-process">The Appeals Process</h2>

<p>I submitted my appeal.  I waited.  One day turned into a week.  A week turned into a month.  A month turned into four months.  The status never changed.  No email.  No update.  No human being at any point in this process.</p>

<p>I checked periodically, hoping that someone - anyone - had looked at it.  Nothing.  The appeal just sits there, presumably in a queue that nobody is reading.</p>

<h2 id="creating-a-new-account">Creating a New Account</h2>

<p>Today I gave up on the appeal and tried creating a new account with a different email address.  Facebook asked me to verify my face.  Fine.  I did the verification.  “We’ll confirm within 24 hours,” they said.</p>

<p>Within 4 hours, my new account was blocked too.</p>

<p>No reason given.  No appeal.  Nothing.  Just blocked.</p>

<p>So now I have two Facebook accounts: one locked for 112 days with a stale appeal, and one blocked within 24 hours of creation.  I cannot use either.</p>

<h2 id="where-are-the-humans">Where Are the Humans?</h2>

<p>Can I submit a support query? No.  Can I email someone? No.  Can I call someone? No.  Can I chat with a support agent? No.</p>

<p>There is no path to a human being.  Every avenue leads back to the same automated loop: submit an appeal, wait forever, hear nothing.</p>

<p>This is a platform with over three billion users.  I’m not naive enough to think they can offer bespoke support to everyone.  But there is a difference between “limited support” and “no support at all.” Facebook has chosen the latter.</p>

<h2 id="what-is-going-on-with-automation-at-facebook">What Is Going On With Automation at Facebook?</h2>

<p>I genuinely want to understand what is happening inside Meta’s automation systems.  My original account was locked for no reason I can identify.  The appeals process promises a timeline it has no intention of meeting.  A brand new account with a fresh email and verified face gets nuked within a day.</p>

<p>What is the automation flagging? My IP address? My device? My face? Something about the way I type my name? There is no way to know, because there is no transparency and there is no feedback.</p>

<p>This is what happens when you automate everything and remove every human from the loop.  The system becomes a black box that makes decisions nobody can explain, nobody can challenge, and nobody can reverse.</p>

<h2 id="why-i-even-care">Why I Even Care</h2>

<p>Here’s the embarrassing part: I don’t actually like Facebook.  I haven’t voluntarily scrolled a Facebook feed in years.  But my extended family still uses it to communicate.  Group chats, photo sharing, event planning - for some reason, they’ve settled on Facebook as the family communication platform.</p>

<p>So I’m not fighting to get back on Facebook because I miss the content.  I’m fighting to stay connected to my family.  And Facebook has made that impossible while offering zero recourse.</p>

<h2 id="advice">Advice?</h2>

<p>If anyone has managed to get a locked Facebook account restored, I’m all ears.  I’ve exhausted every automated pathway I can find.  The only option I haven’t tried is knowing someone who works at Meta - which, as far as I can tell, is the actual support process.</p>

<p>In the meantime, I’m trying to convince my family to move to Signal or WhatsApp.  Ironic, given that Meta owns WhatsApp.  But at least WhatsApp hasn’t locked me out.  Yet.</p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[Facebook locked me out on November 18 2025. Their appeals process said it would take one day. That was 112 days ago. I still can't get in, I can't create a new account, and there is no human to talk to.]]></summary></entry><entry><title type="html">Fixing Longhorn CrashLoopBackOff on k3s: An nftables Cross-Node Networking Nightmare</title><link href="https://www.jonathanclarke.ie/2026/02/16/longhorn-crashloopbackoff-fix.html" rel="alternate" type="text/html" title="Fixing Longhorn CrashLoopBackOff on k3s: An nftables Cross-Node Networking Nightmare" /><published>2026-02-16T00:00:00+00:00</published><updated>2026-02-16T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2026/02/16/longhorn-crashloopbackoff-fix</id><content type="html" xml:base="https://www.jonathanclarke.ie/2026/02/16/longhorn-crashloopbackoff-fix.html"><![CDATA[<p>When adding a new agent node to a k3s cluster, everything looked fine on the surface; the node joined, pods were scheduled, and local workloads ran.  But Longhorn’s manager pods started CrashLoopBackOff-ing with increasingly cryptic errors.  What followed was a multi-layered debugging session that uncovered a fundamental networking issue hiding behind misleading error messages.</p>

<h2 id="the-environment">The Environment</h2>

<ul>
  <li><strong>k3s</strong> cluster with two nodes:
    <ul>
      <li><strong>knight</strong> (control-plane, Debian 11, kernel 5.10)</li>
      <li><strong>bishop</strong> (agent node, Ubuntu 25.04, kernel 6.14)</li>
    </ul>
  </li>
  <li><strong>Longhorn v1.6.0</strong> for persistent storage</li>
  <li><strong>Flannel</strong> VXLAN overlay networking (default k3s CNI)</li>
</ul>

<h2 id="the-symptoms">The Symptoms</h2>

<p>After adding bishop as a new agent node, Longhorn pods on bishop entered <code class="language-plaintext highlighter-rouge">CrashLoopBackOff</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>longhorn-manager-xdbbf    0/1   CrashLoopBackOff
longhorn-csi-plugin-g9hld 0/3   CrashLoopBackOff
</code></pre></div></div>

<h3 id="red-herring-1-missing-open-iscsi">Red Herring #1: Missing open-iscsi</h3>

<p>The first error was straightforward:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error starting manager: Failed environment check, please make sure you have
iscsiadm/open-iscsi installed on the host
</code></pre></div></div>

<p>Fixed with <code class="language-plaintext highlighter-rouge">sudo apt install -y open-iscsi &amp;&amp; sudo systemctl enable --now iscsid</code> on bishop. But the problems continued.</p>

<h3 id="red-herring-2-stale-webhook-tls-certificates">Red Herring #2: Stale Webhook TLS Certificates</h3>

<p>After fixing iscsi, the managers crashed with webhook timeout errors:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failed to check endpoint https://longhorn-conversion-webhook.longhorn-system.svc:9501/v1/healthz:
context deadline exceeded
</code></pre></div></div>

<p>The webhook TLS certificates were 547 days old from the original install. Deleting and letting them regenerate helped briefly:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl delete secret <span class="nt">-n</span> longhorn-system longhorn-webhook-ca longhorn-webhook-tls
</code></pre></div></div>

<p>But the timeouts returned.</p>

<h3 id="red-herring-3-stale-webhook-configurations">Red Herring #3: Stale Webhook Configurations</h3>

<p>Old <code class="language-plaintext highlighter-rouge">ValidatingWebhookConfiguration</code> and <code class="language-plaintext highlighter-rouge">MutatingWebhookConfiguration</code> resources from v1.6.0 were blocking the upgrade migration. Deleting them helped the managers start momentarily, but they’d crash again within minutes.</p>

<h2 id="the-real-problem-nftables-blocking-vxlan-traffic">The Real Problem: nftables Blocking VXLAN Traffic</h2>

<p>After hours of chasing webhook and CSI errors, a simple DNS test from a pod on knight revealed the truth:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nslookup longhorn-backend.longhorn-system.svc.cluster.local
<span class="p">;;</span> connection timed out<span class="p">;</span> no servers could be reached
</code></pre></div></div>

<p>DNS was completely broken for cross-node communication. CoreDNS was running on bishop, and pods on knight couldn’t reach it. A ping test confirmed:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># From a pod on knight, pinging bishop's CoreDNS</span>
<span class="nv">$ </span>ping 10.42.5.17
2 packets transmitted, 0 packets received, 100% packet loss
</code></pre></div></div>

<p>Yet node-to-node communication worked fine:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ping 192.168.1.84  <span class="c"># bishop's node IP</span>
2 packets transmitted, 2 received, 0% packet loss
</code></pre></div></div>

<h3 id="the-root-cause-dual-iptablesnftables-conflict">The Root Cause: Dual iptables/nftables Conflict</h3>

<p>Both nodes were running <strong>iptables-legacy</strong> and <strong>nftables</strong> simultaneously. k3s and flannel configure their rules in iptables-legacy, but the actual packet filtering was happening in nftables – which had a <code class="language-plaintext highlighter-rouge">FORWARD policy drop</code> that flannel knew nothing about.</p>

<p>On knight:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>table ip filter {
    chain FORWARD {
        type filter hook forward priority filter; policy drop;
        ...
        counter packets 0 bytes 0 jump FLANNEL-FWD
    }
}
</code></pre></div></div>

<p>Flannel’s <code class="language-plaintext highlighter-rouge">FLANNEL-FWD</code> chain had the right rules, but traffic never reached it – it was dropped by the policy before getting there.</p>

<p>On bishop, it was even worse – the <code class="language-plaintext highlighter-rouge">INPUT</code> chain had <code class="language-plaintext highlighter-rouge">policy drop</code>, which meant VXLAN UDP packets (port 8472) were being dropped before they could even be decapsulated:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>table ip filter {
    chain INPUT {
        type filter hook input priority filter; policy drop;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">tcpdump</code> confirmed: knight was sending VXLAN packets out, bishop was receiving them, but bishop’s firewall dropped them at INPUT before the kernel could decapsulate them into flannel traffic.</p>

<h2 id="the-fix">The Fix</h2>

<h3 id="knight-forward-only">Knight (FORWARD only)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nft insert rule ip filter FORWARD iifname <span class="s2">"flannel.1"</span> accept
<span class="nb">sudo </span>nft insert rule ip filter FORWARD oifname <span class="s2">"flannel.1"</span> accept
<span class="nb">sudo </span>nft insert rule ip filter FORWARD iifname <span class="s2">"cni0"</span> accept
<span class="nb">sudo </span>nft insert rule ip filter FORWARD oifname <span class="s2">"cni0"</span> accept
</code></pre></div></div>

<h3 id="bishop-input--forward">Bishop (INPUT + FORWARD)</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nft insert rule ip filter INPUT udp dport 8472 accept
<span class="nb">sudo </span>nft insert rule ip filter FORWARD iifname <span class="s2">"flannel.1"</span> accept
<span class="nb">sudo </span>nft insert rule ip filter FORWARD oifname <span class="s2">"flannel.1"</span> accept
<span class="nb">sudo </span>nft insert rule ip filter FORWARD iifname <span class="s2">"cni0"</span> accept
<span class="nb">sudo </span>nft insert rule ip filter FORWARD oifname <span class="s2">"cni0"</span> accept
</code></pre></div></div>

<h3 id="making-it-persistent">Making It Persistent</h3>

<p>nftables rules don’t survive reboots. Since k3s recreates its own chains on startup, the fix needs to run after k3s. A systemd oneshot service does the job:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /usr/local/bin/k3s-nftables-fix.sh</span>
<span class="c">#!/bin/bash</span>
<span class="nb">sleep </span>5
nft insert rule ip filter INPUT udp dport 8472 accept  <span class="c"># bishop only</span>
nft insert rule ip filter FORWARD oifname <span class="s2">"cni0"</span> accept
nft insert rule ip filter FORWARD iifname <span class="s2">"cni0"</span> accept
nft insert rule ip filter FORWARD oifname <span class="s2">"flannel.1"</span> accept
nft insert rule ip filter FORWARD iifname <span class="s2">"flannel.1"</span> accept
</code></pre></div></div>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/systemd/system/k3s-nftables-fix.service
</span><span class="nn">[Unit]</span><span class="w">
</span><span class="py">Description</span><span class="p">=</span><span class="s">Fix nftables rules for k3s cross-node networking</span>
<span class="py">After</span><span class="p">=</span><span class="s">k3s.service  # or k3s-agent.service for agent nodes</span>
<span class="w">
</span><span class="nn">[Service]</span><span class="w">
</span><span class="py">Type</span><span class="p">=</span><span class="s">oneshot</span>
<span class="py">ExecStart</span><span class="p">=</span><span class="s">/usr/local/bin/k3s-nftables-fix.sh</span>
<span class="py">RemainAfterExit</span><span class="p">=</span><span class="s">yes</span>
<span class="w">
</span><span class="nn">[Install]</span><span class="w">
</span><span class="py">WantedBy</span><span class="p">=</span><span class="s">multi-user.target</span>
</code></pre></div></div>

<h2 id="upgrading-longhorn-after-the-fix">Upgrading Longhorn After the Fix</h2>

<p>With networking fixed, we upgraded Longhorn from the EOL v1.6.0 through the required sequential path:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>v1.6.0 → v1.7.2 → v1.8.1 → v1.9.2 → v1.10.1 → v1.11.0
</code></pre></div></div>

<p>Each step was a simple <code class="language-plaintext highlighter-rouge">kubectl apply</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl apply <span class="nt">-f</span> https://raw.githubusercontent.com/longhorn/longhorn/v1.7.2/deploy/longhorn.yaml
<span class="c"># Wait for all pods healthy, then repeat for next version</span>
</code></pre></div></div>

<p>Longhorn enforces sequential minor version upgrades – you cannot skip versions.</p>

<h2 id="lessons-learned">Lessons Learned</h2>

<ol>
  <li>
    <p><strong>When pods crash with service connectivity errors, test the network first.</strong> A simple <code class="language-plaintext highlighter-rouge">nslookup</code> or <code class="language-plaintext highlighter-rouge">ping</code> from a pod would have saved hours of chasing webhook and CSI errors.</p>
  </li>
  <li>
    <p><strong>Check for dual iptables/nftables.</strong> Modern Linux distributions often have both installed. The warning <code class="language-plaintext highlighter-rouge">iptables-legacy tables present, use iptables-legacy to see them</code> is a red flag that two firewall systems are competing.</p>
  </li>
  <li>
    <p><strong>VXLAN needs explicit firewall rules.</strong> If your FORWARD or INPUT policy is <code class="language-plaintext highlighter-rouge">drop</code>, flannel’s VXLAN overlay will silently fail. You need to allow UDP 8472 on INPUT and flannel.1/cni0 interfaces on FORWARD.</p>
  </li>
  <li>
    <p><strong>Node-to-node ping working doesn’t mean pod networking works.</strong> ICMP between node IPs uses the physical network. Pod traffic uses VXLAN encapsulation, which traverses different firewall chains.</p>
  </li>
  <li>
    <p><strong>Longhorn error messages are misleading during network issues.</strong> Webhook timeouts, CSI connection failures, and admission controller errors all pointed at Longhorn problems, but the root cause was always the network.</p>
  </li>
</ol>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[When adding a new agent node to a k3s cluster, everything looked fine on the surface; the node joined, pods were scheduled, and local workloads ran. But Longhorn's manager pods started CrashLoopBackOff-ing with increasingly cryptic errors. What followed was a multi-layered debugging session that uncovered a fundamental networking issue hiding behind misleading error messages.]]></summary></entry><entry><title type="html">Better Late Than Never: My Goals for 2026</title><link href="https://www.jonathanclarke.ie/2026/02/11/resolutions-for-2026.html" rel="alternate" type="text/html" title="Better Late Than Never: My Goals for 2026" /><published>2026-02-11T00:00:00+00:00</published><updated>2026-02-11T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2026/02/11/resolutions-for-2026</id><content type="html" xml:base="https://www.jonathanclarke.ie/2026/02/11/resolutions-for-2026.html"><![CDATA[<p>I know, I know.  We are already well into the year.  Most people have already abandoned their resolutions by now, but I’m just getting mine down on “paper.”  I actually wrote them out on December 31.  Posting this late probably says something about me, but honestly, these are the goals that stuck around after the January buzz wore off, so maybe that counts for more.</p>

<p>Here is what I am committing to:</p>

<h3 id="1-complete-project-300">1. Complete Project 300</h3>

<p>This is the big one that everything else feeds into. “Project 300” means showing up consistently, even when life gets busy.  I don’t need perfect sessions, I just need to keep going.  If I can nail this, the rest of the physical goals on this list become a lot more realistic.</p>

<ul>
  <li><em>You can follow my live progress and see my effort here: <a href="/fitness">/fitness</a></em></li>
</ul>

<h3 id="2-master-my-vitals--drop-below-100kg">2. Master My Vitals &amp; Drop Below 100kg</h3>

<p>This goes hand-in-hand with Project 300, but it’s the measurable side. I want to get my blood pressure, resting heart rate, and metabolic markers properly under control, and getting below 100kg is a big milestone there. I also want to feel lighter on my feet, especially for the treks I have planned.</p>

<h3 id="3-read-30-books">3. Read 30 Books</h3>

<p>I want to get back into reading properly. 30 books works out to roughly one every 12 days, which is ambitious but doable. Planning a mix of professional stuff, biographies, and some fiction thrown in.</p>

<ul>
  <li><em>You can follow my live progress and see my reading list here: <a href="/reading">/reading</a></em></li>
</ul>

<h3 id="4-trek-up-to-australian-camp-with-the-family">4. Trek up to Australian Camp with the family</h3>

<p>We are heading to the “Australian Camp” trail. It’s a proper mountain experience without the extreme altitude, so it’s ideal for the whole family.  I want to get the kids out on the trail - the walking, the lodges, no screens.  Should be great.</p>

<h3 id="5-trek-pikey-peak-with-my-wife">5. Trek Pikey Peak with My Wife</h3>

<p>Just Kisu and me on this one. Pikey Peak is known for having one of the best views of Everest in the lower Solukhumbu region.  It’s steep, it’s beautiful, and it’s a chance for us to get away from everything for a few days.</p>

<h3 id="6-trek-everest-base-camp-ebc">6. Trek Everest Base Camp (EBC)</h3>

<p>The bucket list item. Later in the year, I will be heading to the foot of the world’s highest mountain. It’s going to test everything (and that sub-100kg goal will matter), but standing at Base Camp is something I’ve wanted to do for years.  All the training earlier in the year leads here.</p>

<h3 id="7-attend-4-conferences">7. Attend 4 Conferences</h3>

<p>With all the hiking and running, I don’t want to neglect the professional side. Four conferences this year.  I want to get out of my bubble, meet people in person, and bring fresh ideas back to my work.</p>

<p>I’ve already attended Ruby Conf Bangkok, AgentCon Bangkok is planned for this month.  I’ve two more to attend, let me know if you have any recommendations, interested in business, development, AI and overall good vibes.</p>

<h3 id="8-half-marathon-at-fishtail">8. Half Marathon at Fishtail</h3>

<p>A half marathon in the shadow of Machhapuchhre (Fishtail Mountain) is a different beast to running one on flat pavement.  Training for that trail is going to keep me honest all year.  Thankfully I’ll be doing this with loads of friends, really looking forward to this one.</p>

<p><strong>So, that’s the list.</strong>  A lot of travel, a lot of sweat.  Now that this is posted, I have no excuse.  I’ll be updating my progress as I go.</p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[Posting my 2026 resolutions six weeks late. Treks, books, a half marathon, and a lot of gym sessions.]]></summary></entry><entry><title type="html">To Australia Camp and back again; Or how to endure the gravity of giants</title><link href="https://www.jonathanclarke.ie/2026/01/15/to-australia-camp-and-back-again-or-how-to-endure-the-gravity-of-giants.html" rel="alternate" type="text/html" title="To Australia Camp and back again; Or how to endure the gravity of giants" /><published>2026-01-15T00:00:00+00:00</published><updated>2026-01-15T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2026/01/15/to-australia-camp-and-back-again-or-how-to-endure-the-gravity-of-giants</id><content type="html" xml:base="https://www.jonathanclarke.ie/2026/01/15/to-australia-camp-and-back-again-or-how-to-endure-the-gravity-of-giants.html"><![CDATA[<p>To leave Kathmandu is not merely a journey of distance, but of density.  You must peel yourself away from the sticky, chaotic gravity of the valley, shedding layers of dust and static as the highway unspools like a bruised ribbon beneath the wheels.  Construction constantly abounds for the first half, then, smooth roads after the goats are seeing heading up to their end in Manakamana.  The drive to Pokhara is a shedding of skin, a long, winding exhale that lasts all day, until the grey noise of the city is finally replaced by the silent, looming promise of the Annapurnas.</p>

<p>My family did not go to conquer the mountain; we went to introduce ourselves to it.  Our first family trek, hopefully one with a gentle slope.</p>

<p>The trek from Ghatte Khola to Dhampus is a staircase carved by time and patience.  For someone trying to remember the language of his own body, every stone step was a syllable, a heavy punctuation mark demanding breath.  Gravity is a jealous god here.  It pulls at the calves and bargains with the lungs.  But we climbed and as we ascended, the air began to thin and sweeten, tasting less of exhaust and more of pine resin and cold stone.  The world simplified.  There was only the step, the breath, and the sky.</p>

<p>And there were my twins.</p>

<p>My girls turned six in the shadow of the giants.  This was their first birthday celebrated not with cake and sugar in a crowded room with flower showers, but with the earth beneath their ragged shoes and the wind in their hair.  Six is a such a magical threshold.  No longer toddlers stumbling through a mysterious world, but young explorers claiming it.  While I bargained with gravity, they seemed immune to it.  They were sparks of kinetic joy, bounding up the trail to Australian Camp as if the mountain were simply a playground built specifically for them by the gods.</p>

<p>To watch your children turn six against a backdrop of eternal snow is to feel the devastating speed of time.</p>

<p>At Australian Camp, the clouds performed their slow, theatrical dance, parting perfectly to reveal the jagged, white teeth of the Himalayas.  Machhapuchhre did not look like rock and ice; it looked like a frozen prayer piercing the blue; the fishtail was well named.  We stood there, our family of four, small and temporary against the geology of forever.</p>

<p>We celebrated with the currency of presence.  There were no notifications, no urgent emails, no deadlines.  Just the golden light of the afternoon hitting the grass, the laughter of our families of children who have just discovered they are strong enough to climb a mountain, and the silent approval of the peaks.</p>

<p>The descent back to Dhampus and subequently Ghatte Khola was a return to the heavier air, the knees singing the song of the descent, the brake of the body engaged against the pull of the earth.  My stick digging into the stone bending to my weight.  I gripped their hands over the loose stones, anchoring them against the pull of the slope, and wondered if this was the last time they would need my steadying, or if the next mountain would belong entirely to their own balance.  We carried the fatigue like a trophy.  Dandelions floated in the breeze aided with the breath of my children.</p>

<p>We returned to the world of roads and engines, but we came back so much lighter, bonded tighter.  We left a year of childhood on the ridge, and we brought back the silence of the high places, tucked into our chests like a secret, enough to sustain us until the next journey.</p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[To leave Kathmandu is to shed the city's chaotic gravity for the silent promise of the Annapurnas. We climbed to Australian Camp not to conquer the mountain, but to celebrate a milestone: my twins turning six and to my embarking on a new sabbatical. While I bargained with the jealous god of gravity on the stone steps, they bounded up as sparks of kinetic joy; a reminder that watching your children grow against a backdrop of eternal snow is to feel the devastating speed of time.]]></summary></entry><entry><title type="html">Stop and Breathe: Why I Prioritised Health Over Career</title><link href="https://www.jonathanclarke.ie/2026/01/07/stop-and-breathe-why-i-prioritised-health-over-career.html" rel="alternate" type="text/html" title="Stop and Breathe: Why I Prioritised Health Over Career" /><published>2026-01-07T00:00:00+00:00</published><updated>2026-01-07T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2026/01/07/stop-and-breathe-why-i-prioritised-health-over-career</id><content type="html" xml:base="https://www.jonathanclarke.ie/2026/01/07/stop-and-breathe-why-i-prioritised-health-over-career.html"><![CDATA[<p>On January 1, 2026, I officially left my position as Director of Engineering at <a href="https://www.houzz.com">Houzz</a>.  This came four and a half years after a successfull acquisition of <a href="https://www.conx.co">ConX</a></p>

<p>Why? Well, I want to focus on my health and fitness. I’ve been quite unwell the past few months - a bad dose of influenza coupled with bronchitis; it wasn’t a good time. It felt like I was sick for the past few years continuously. My stress levels were sky-high, my sleep patterns disturbed, and frankly, I was perpetually exhausted.</p>

<p>So what next? I don’t know. Initially, before I handed in my notice, I had some vague idea of trekking, working out, and reading over the next 12 months. This has recently been expanded out with some specificity.</p>

<p>I’ve been living in Nepal since 2015. I see the foothills of Kathmandu almost every day. And yet, for the last few years, the only mountains I’ve been climbing are metaphorical ones: technical debt, organizational scaling, and quarterly engineering targets.</p>

<p>I turned 43 recently, and I had to face a harsh reality: I found myself thriving within a major software organization by all accounts successful yet I am arguably the most unfit I have ever been.</p>

<p>There is a strange irony in living in the trekking capital of the world while spending 10 hours a day sitting in a chair in Lalitpur. I realized that if I didn’t focus on my health soon, the crash was going to be permanent.</p>

<p>So, I made a decision to press pause.</p>

<p>I have left my role to take a dedicated break. This isn’t a holiday; it’s a course correction. My goal for the next few months is simple but brutal: fix my body, expand my mind, and slow down.</p>

<p>To do that, I’m finally using the backyard I’ve neglected. Here is the plan.</p>

<p>Sometime this month, we’re starting with Australian Camp. For those who don’t know, this is a beautiful spot near Pokhara, sitting at around 2,000 meters. It’s not “Australia Base Camp” (though the confusion happens), and it’s certainly not Everest. But right now, for the unfit version of me, it’s exactly what I need.</p>

<p>This is the soft launch. It’s about getting the legs moving again, breathing clean air, and remembering what it feels like to walk on something other than flat pavement. It’s low stakes, high reward - panoramic views of the Annapurna range without the oxygen deprivation. I’ll be with my wife and kids, and I’m really looking forward to it.</p>

<p>Once the legs remember how to work, I’m heading to Pikey Peak.</p>

<p>This takes us up to 4,065 meters. This is where the real work begins. It’s a lower Khumbu trek that offers what Edmund Hillary claimed was his favorite view of Everest. It’s steeper, harder, and less commercial than the main drag. This will be the first real test of my lungs. It’s the bridge between “going for a walk” and “trekking.” My wife will join me on this one while I leave the kids behind with their grandmother.</p>

<p>I added an amended stretch goal last week to accompany a friend to Everest Base Camp. This has become the anchor of my sabbatical at 5,364m. Living in Nepal and not doing EBC feels like living in Paris and refusing to look at the Eiffel Tower because “it’s too busy.” It’s time to go. This isn’t just about the altitude; it’s about the duration. It’s weeks of disconnection, tea house living, and walking day after day. It’s the deep reset I need to shake off years of startup burnout.</p>

<p>If trekking is the training, then this year’s final exam will be the Fishtail Half Marathon. Running on trails in the shadow of Machhapuchhre (Fishtail Mountain) is a different beast entirely from hiking. It requires cardio fitness and agility that I currently do not possess. But having this date on the calendar forces me to stay honest. I can’t just stroll my way through this break; I have to train. Thankfully, I have many friends who are also signed up to run this race, so I’ll be in good company.</p>

<p>Between the climbs and the recovery days, I’m finally attacking my <a href="/reading/">reading list</a>. Physical books, audiobooks, epubs.  I’ve lined up a mix of speculative fiction (because I miss getting lost in other worlds) and resilience - focused non-fiction. I’m aiming for 30 books this year. Let me know if you have any recommendations.</p>

<p>Leaving any role after such a long time is scary; the anxiety of all of this has definitely hit me early on. There is always the fear of stepping off the career treadmill. But right now, the risk of not doing this is higher.</p>

<p>I’m taking this time to be a better father, a better husband, and eventually, a better person. But for now, I’m just a guy trying to get up a hill with a book in my bag.</p>

<p>See you on the trails.  You can follow my fitness journey at <a href="/fitness">/fitness</a></p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[No more OKRs, just elevation gains. I've pressed pause on my career to trek Pikey Peak, Everest Base Camp, and finally use the amazing backyard I've neglected.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.jonathanclarke.ie/public/images/jc_logo.png" /><media:content medium="image" url="https://www.jonathanclarke.ie/public/images/jc_logo.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">George Orwell - the Nostradamus of his time</title><link href="https://www.jonathanclarke.ie/2026/01/03/george-orwell-the-nostradamus-of-his-time.html" rel="alternate" type="text/html" title="George Orwell - the Nostradamus of his time" /><published>2026-01-03T00:00:00+00:00</published><updated>2026-01-03T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2026/01/03/george-orwell-the-nostradamus-of-his-time</id><content type="html" xml:base="https://www.jonathanclarke.ie/2026/01/03/george-orwell-the-nostradamus-of-his-time.html"><![CDATA[<p>I discover with abject horror that <a href="https://www.ndtv.com/world-news/nasas-largest-library-to-permanently-close-on-jan-2-books-will-be-tossed-away-10170584">NASA’s Largest Library To Permanently Close On Jan 2</a>.</p>

<p>NASA’s largest library at the Goddard Space Flight Center will close permanently on Jan 2 under the Trump administration’s reorganisation plans.  The closure of the 100,000 volume library is part of the Donald Trump administration’s reorganisation drive, under which 13 buildings and over 100 science and engineering laboratories will be shut down on the 1.270-acre campus by March 2026</p>

<p>I’m lead to think back on 1984 - George Orwell:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"Every record has been destroyed or falsified, every book rewritten, every picture has been repainted, every statue and street building has been renamed, every date has been altered. And the process is continuing day by day and minute by minute. History has stopped. Nothing exists except an endless present in which the Party is always right."
</code></pre></div></div>

<p>Nostradamus wrote riddles that require faith to decode.  George Orwell wrote a warning that requires courage to read.  Orwell wasn’t trying to tell us what would happen; he was desperate to show us what could happen if we stopped paying attention.</p>

<p>In that sense, he is the most effective prophet we’ve ever had. He didn’t want to be right. He wanted us to prove him wrong.  We are experiencing a “perpetual crisis” or the forever war.  Newspeak and fake news dominate our shrinking media.  News feeds are tailored to confirm our biases through the algorithm, creating separate realities for different demographics.  We see history rewritten or context scrubbed in real-time.  We see the denial of visible reality in favor of party-line loyalty.  We carry the Telescreen in our pockets. We wear it on our wrists. LLMs have become our “smart” assistants.</p>

<p>Nostradamus relied on poetic ambiguity allowing readers to project whatever they want onto the text. A sham!</p>

<p>The reason Orwell reads like a prophet today is not because he had supernatural vision. It is because he was a sociologist of the worst-case scenario.  We are truly living in interesting times.</p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[Every record has been destroyed or falsified, every book rewritten, every picture has been repainted, every statue and street building has been renamed, every date has been altered. And the process is continuing day by day and minute by minute. History has stopped. Nothing exists except an endless present in which the Party is always right.]]></summary></entry><entry><title type="html">My progress reading in 2025</title><link href="https://www.jonathanclarke.ie/2025/12/31/my-progress-reading-in-2025.html" rel="alternate" type="text/html" title="My progress reading in 2025" /><published>2025-12-31T00:00:00+00:00</published><updated>2025-12-31T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2025/12/31/my-progress-reading-in-2025</id><content type="html" xml:base="https://www.jonathanclarke.ie/2025/12/31/my-progress-reading-in-2025.html"><![CDATA[<p>I set out with a simple goal for <a href="/reading/2025">2025</a> - to read 12 books to stop the endless doom-scrolling and being constantly stuck in the YouTube shorts algorithm. I somehow failed that goal in the best way possible - I managed to accidentally read 26. That’s an enormous return on my investment, which frankly, makes this the most successful project I managed all year.</p>

<p>Looking back at the stack, my reading personality was all over the place. If my bookshelf were a person, it would be a tech bro who is trying to escape to space while simultaneously processing deep-seated childhood trauma.</p>

<p>Here is the breakdown of my year in pages:</p>

<h3 id="escapist">Escapist:</h3>

<p>I spent a huge chunk of the year off-planet or in alternate dimensions (Project Hail Mary, Neuromancer, The Dark Tower). It was a necessary detox from the “real world” news cycle.  If I saw another Trump news dominated cycle I was going to go crazy.</p>

<h3 id="optimizer">Optimizer:</h3>

<p>I didn’t completely abandon the grind. Between Rocket Fuel, The Mom Test, and reading about the potential Epstein file Bill Gates, I kept one foot in the business world - but I focused on how to build things rather than just reading hot takes about them.  Rocket Fuel was not a great read - probably best to avoid that one. Leaders Eat Last was like reading my own personal thoughts from a page (scary).  What they don’t teach you at Harvard Business School - an oldie but a goodie; deep in ancient times of sports deals but certainly a few things stuck with me about dealing with people.</p>

<h3 id="survivor">Survivor:</h3>

<p>I clearly had a thing for resilience this year. From Can’t Hurt Me to Educated and The Boys in the Boat, I spent a lot of time reading about people who refused to break.  Perhaps I saw myself mirrored in these pages.</p>

<h2 id="lines-that-stuck-with-me">Lines That Stuck With Me</h2>

<p>Out of the thousands of pages I turned, these are the quotes that are still ringing in my head:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"The sky above the port was the color of television, tuned to a dead channel."
</code></pre></div></div>

<p>Neuromancer, William Gibson (Still the coolest opening line in science fiction history.)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"You can love someone and still choose to say goodbye to them. You can miss a person every day, and still be glad that they are no longer in your life."
</code></pre></div></div>

<p>Educated, Tara Westover</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"So it goes."
</code></pre></div></div>

<p>Slaughterhouse-Five, Kurt Vonnegut - and so it goes and goes and goes for obvious reasons.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"THERE’S NO JUSTICE. THERE’S JUST ME."
</code></pre></div></div>

<p>Mort, Terry Pratchett (Nobody writes Death quite like Pratchett - always has been my favourite character.)</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"The most difficult conversation is the one you have to have with yourself."
</code></pre></div></div>

<p>Can’t Hurt Me, David Goggins - hard conversations were certainly had about my physical and mental health this year, I really hope to address this further in 2026.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"Power rarely announces itself as cruelty; it arrives disguised as efficiency."
</code></pre></div></div>

<p>Careless People: A Cautionary Tale of Power, Greed, and Lost Idealism by Sarah Wynn-Williams.  We have a responsibility to do better than those who came before us and to hold those who abuse our trust accountable.</p>

<p>I promised myself I’d push harder this year, and looking at this list, perhaps I finally did.  I’ll do better again in <a href="/reading/2026">2026</a>. Check out my <a href="/reading/wishlist">wishlist</a></p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[I set out with a simple goal for 2025 - to read 12 books to stop the endless doom-scrolling and being constantly stuck in the YouTube shorts algorithm. I somehow failed that goal in the best way possible - I managed to accidentally read 26. That’s an enormous return on my investment, which frankly, makes this the most successful project I managed all year.]]></summary></entry><entry><title type="html">Project 300: 109kg to the Finish Line</title><link href="https://www.jonathanclarke.ie/2025/12/26/300-days.html" rel="alternate" type="text/html" title="Project 300: 109kg to the Finish Line" /><published>2025-12-26T00:00:00+00:00</published><updated>2025-12-26T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2025/12/26/300-days</id><content type="html" xml:base="https://www.jonathanclarke.ie/2025/12/26/300-days.html"><![CDATA[<p>I stepped on the scale this morning and the number stared back at me, unblinking.</p>

<p><strong>109kg.</strong></p>

<p>I’m 43. I’m unfit. And honestly? My body feels every single one of those kilograms. The knees creak when I get up from the couch, the cholesterol numbers are moving in the wrong direction, and I’m constantly relying on medication just to keep things steady.</p>

<p>It’s time to stop drifting.</p>

<p>I’m calling this <strong>Project 300</strong>.</p>

<h2 id="the-target">The Target</h2>

<p>On December 6, 2026, I plan to be at the starting line of the <strong>Fishtail Race</strong> in Pokhara.</p>

<p>If you know Nepal trails, you know this isn’t a gentle jog in the park. It’s 25 kilometers of brutal, beautiful terrain. We’re talking about 1,400 meters of elevation gain, ridge lines that make you dizzy, and endless stone steps that destroy your quads on the way down.</p>

<p>Trying to run that today? I wouldn’t make it to the first checkpoint. I’d be gasping for air before I even left the lakeside.</p>

<p>To get there, I have roughly 300 days of actual, usable training time starting December 27th.</p>

<h2 id="the-plan">The Plan</h2>

<p>This isn’t about getting a “beach body.” I’ve got two kids and a serious weakness for a good mutton thali - I’m realistic. This is about capacity. It’s about building a body that doesn’t just survive the climb but actually enjoys it.</p>

<p>So, here is what the next 300 days look like:</p>

<p><strong>1. Dry January (to start)</strong></p>

<p>The holidays were incredible. We hosted a “Chosen Family” Christmas potluck here in Kathmandu, the house was full of friends, the food was amazing, and the drinks were flowing. But the sluggishness that follows? I’m done with it. Alcohol is the first thing to go. I need clear mornings and better recovery.</p>

<p><strong>2. The Grind</strong></p>

<p>I’m currently training on a treadmill that breaks down almost as often as I do, and an indoor bike. But the goal is to get out onto the actual hills around Lalitpur. You can’t simulate Nepal’s terrain in a gym. You have to feel the rocks under your feet.  HHHH - Himalayan Hash House Harriers to the rescue; I’m planning on joining the run in the last Saturday in January.</p>

<p><strong>3. The Health Metrics</strong>
I want off the meds. I want the doctor to look at my cholesterol results and be bored. I want to be able to tie my shoes without making a noise that sounds like a deflating tire.</p>

<h2 id="why-blog-about-it">Why Blog About It?</h2>

<p>Because it’s easy to quit when you do it in silence.</p>

<p>If I tell myself I’m going to train, I might skip it when it’s cold and raining. If I tell <em>you</em>, whomever you are, well I’m going to train, I look like an idiot if I don’t follow through.</p>

<p>I’m going to share the ugly parts too. The days where progress stalls, the injuries (hopefully minor), and the days I just really, really want a beer.</p>

<p>300 days of discipline. 109kg starting weight. One big mountain to climb.</p>

<p>Let’s see what happens.  Cheer me on over at <a href="https://mastodon.ie/@jonathanclarke">mastodon.</a></p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[109kg. 43 years old. Unfit. I have 300 days to transform my health and train for Nepal's 25km Fishtail Race. This is the start of Project 300.]]></summary></entry><entry><title type="html">Debloating the Amazon Fire HD 8 (10th Gen)</title><link href="https://www.jonathanclarke.ie/2025/10/07/debloating-amazon-fire-8-hd.html" rel="alternate" type="text/html" title="Debloating the Amazon Fire HD 8 (10th Gen)" /><published>2025-10-07T00:00:00+00:00</published><updated>2025-10-07T00:00:00+00:00</updated><id>https://www.jonathanclarke.ie/2025/10/07/debloating-amazon-fire-8-hd</id><content type="html" xml:base="https://www.jonathanclarke.ie/2025/10/07/debloating-amazon-fire-8-hd.html"><![CDATA[<p>If you’ve ever owned an Amazon Kindle Fire 8 (Gen 10), you know the struggle - the constant push of Amazon services, the sluggish performance, and the endless bloatware.  I bought 2 tablets for my girls a few years ago, mainly to watch youtube kids videos on an international flight.</p>

<p>Amazon doesn’t make it easy either: blocking OTA updates (over-the-air) is impossible now. They’ve locked it down so tight that even advanced users can’t easily stop unwanted updates or reboots that re-enable their bloatware.</p>

<p>The Amazon Fire tablet runs a heavily modified version of Android called Fire OS.  Instead of giving you a clean Android experience, Amazon turns your tablet into a billboard for its ecosystem - pushing its own services, apps, and ads at every corner.</p>

<p>From the moment you boot up, your tablet is tied to your Amazon account.  Everything, and I mean everything - books, movies, apps, even the search bar - routes through Amazon’s services:</p>

<ul>
  <li>Appstore instead of Google Play (with far fewer apps)</li>
  <li>Amazon Shopping and Prime Video preinstalled and unremovable</li>
  <li>Alexa integration you can’t fully disable</li>
  <li>Default web browser: Silk, which prioritizes Amazon links</li>
  <li>Even the home screen constantly promotes Amazon offers, Prime subscriptions, and shopping suggestions.</li>
</ul>

<p>Fire OS comes bloated with dozens of apps most people never use:</p>
<ul>
  <li>Amazon Music</li>
  <li>Amazon Photos</li>
  <li>Audible</li>
  <li>FreeTime / Kids+</li>
  <li>Goodreads</li>
  <li>GameCircle</li>
  <li>Newsstand</li>
  <li>Amazon Appstore</li>
  <li>Weather</li>
  <li>….the list goes on and on and on… IMDb, Amazon Games, Alexa Shopping Lists.</li>
</ul>

<p>You can “disable” some but most can’t be uninstalled through normal settings.  They sit there consuming RAM, CPU cycles, and storage, slowing down everything else.  Even on a child’s tablet, the lock screen shows Amazon ads for books, movies, and deals.  Not something I want my kids to be subjected to.</p>

<p>Behind the scenes, a swarm of Amazon services run continuously:</p>
<ul>
  <li>com.amazon.device.software.ota (updates and re-enabling apps)</li>
  <li>com.amazon.device.settings.sdk.internal.library</li>
  <li>com.amazon.client.metrics (Amazon’s telemetry and metrics)</li>
  <li>com.amazon.device.messaging</li>
  <li>com.amazon.geo.client.maps</li>
  <li>com.amazon.dcp (device communication platform)</li>
  <li>and many more</li>
</ul>

<p>Together they eat RAM and battery, even when the tablet is idle.</p>

<p>However, there’s good news: you can debloat your Fire tablet safely and breathe new life into it.</p>

<p>Meet Fire Toolbox, a Windows app designed specifically for Amazon Fire tablets.</p>

<p>It lets you:</p>
<ul>
  <li>Remove Amazon apps and services you’ll never use</li>
  <li>Install Google Play Services</li>
  <li>Replace the default Fire Launcher with a clean, fast launcher like Nova.</li>
  <li>Customize system behavior without rooting</li>
</ul>

<p>You can download <a href="https://forum.xda-developers.com/t/windows-tool-fire-toolbox-v31-0.3889604/">Fire Toolbox from XDA</a>.</p>

<h2 id="the-process-debloating-step-by-step">The Process: Debloating Step-by-Step</h2>

<ul>
  <li>Connect your tablet via USB and enable Developer Options → ADB Debugging.</li>
  <li>Run Fire Toolbox and select “Manage Apps” → “Remove Amazon Apps”.</li>
  <li>Uninstall all Amazon bloat: Amazon Appstore, Alexa, Photos, Kids+, Shopping, Music, Silk Browser…the list goes on</li>
  <li>Install Google Play Services using the Toolbox shortcut.</li>
  <li>Replace Fire Launcher with your preferred launcher, I used Nova to give a better look and feel.</li>
  <li>I was unable to complete disable OTA updates so I’ll probably have to do all this again at some point.</li>
  <li>Reboot your tablet.</li>
</ul>

<h2 id="the-result">The Result</h2>

<p>After debloating:</p>
<ul>
  <li>RAM usage dropped by around 30%</li>
  <li>The tablet feels snappier, smoother, and more responsive</li>
  <li>Apps launch faster and battery life improved</li>
  <li>It looks like an actual android tablet</li>
  <li>Most importantly: no more Amazon ads or nags</li>
</ul>

<p>My kids noticed the difference immediately. They can finally use their tablets without being bombarded by Amazon prompts or lag. Simple games and YouTube Kids now run smoothly - ownership feels restored and I don’t need to rush out to buy a new tablet immediately.</p>

<h2 id="final-thoughts">Final Thoughts:</h2>

<p>Amazon’s aggressive control over its Fire tablets makes it clear: these devices are designed to lock users into their ecosystem but with a bit of work - and Fire Toolbox - you can reclaim your device.</p>

<p>For me, the transformation was worth it. The kids are happy, the tablets are fast, and I’ve learned one thing: Fuck Amazon, I’ll never buy another piece of hardware from them ever again (excluding virtual compute).  You own the hardware, but Amazon controls the experience.  Never again.</p>]]></content><author><name>Jonathan Clarke</name><email>hi@jonathanclarke.ie</email></author><summary type="html"><![CDATA[Owning an Amazon Fire 8 tablet can feel like being trapped inside Amazon's ecosystem. The device, running its heavily modified Fire OS, is filled with preinstalled apps, ads, and constant prompts to use Amazon services. All of which slow it down and make it frustrating to use. Even blocking over-the-air updates is no longer possible, as Amazon locks users into its system. What should have been a simple tablet for kids ends up being a sluggish, ad-heavy billboard for Amazon's products instead of a clean Android experience.]]></summary></entry></feed>