<?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: DevLog</title>
    <description>The latest articles on DEV Community by DevLog (@devlog).</description>
    <link>https://dev.to/devlog</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%2F4086093%2F5d250487-05f7-49bc-ac26-846bc42538b1.jpg</url>
      <title>DEV Community: DevLog</title>
      <link>https://dev.to/devlog</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devlog"/>
    <language>en</language>
    <item>
      <title>Fixing npm Global Command Not Found on Windows</title>
      <dc:creator>DevLog</dc:creator>
      <pubDate>Tue, 01 Sep 2026 05:43:14 +0000</pubDate>
      <link>https://dev.to/devlog/fixing-npm-global-command-not-found-on-windows-50e1</link>
      <guid>https://dev.to/devlog/fixing-npm-global-command-not-found-on-windows-50e1</guid>
      <description>&lt;h1&gt;
  
  
  Fixing npm Global Command Not Found on Windows
&lt;/h1&gt;

&lt;p&gt;I recently hit a wall while configuring my development environment on Windows. It's one of those subtle issues that can waste an afternoon if you don't know what to look for. Here's exactly how I diagnosed and fixed a 'command not found' error after a successful global install.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;br&gt;
I decided to add a markdown processing tool to my toolkit by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; markdown-it-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal output confirmed the installation succeeded, displaying a message about added packages. Satisfied, I tried to use it by typing &lt;code&gt;markdown-it-cli&lt;/code&gt; in the command line. Windows immediately responded with: 'markdown-it-cli is not recognized as an internal or external command, operable program or batch file.'&lt;/p&gt;

&lt;p&gt;This error means Windows looked through every folder in its search list and couldn't find an executable with that name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempts and Failures&lt;/strong&gt;&lt;br&gt;
My initial reaction was to suspect the package itself might be broken. I tried running it using &lt;code&gt;npx&lt;/code&gt;, which executes packages without requiring a global install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx markdown-it-cli &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This worked instantly, printing the version number. Since &lt;code&gt;npx&lt;/code&gt; could find and run the tool, I knew the package was functional. The failure was isolated to calling the command directly after a global install.&lt;/p&gt;

&lt;p&gt;I needed to understand why npm couldn't find its own installation. I ran &lt;code&gt;npm root -g&lt;/code&gt;, a command that shows the directory npm uses for global packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\&amp;lt;user&amp;gt;\AppData\Roaming\npm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This told me exactly where npm had placed the executable files. Next, I checked my system's search paths using &lt;code&gt;echo %PATH%&lt;/code&gt;. This command displays the list of directories Windows scans when looking for executables. The output showed entries for Windows system folders, Git, and Java, but the &lt;code&gt;AppData\Roaming\npm&lt;/code&gt; directory was completely missing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root Cause&lt;/strong&gt;&lt;br&gt;
The root cause turned out to be the PATH environment variable all along.&lt;/p&gt;

&lt;p&gt;On Windows, npm does not automatically add its global bin folder to your system PATH in every scenario. Depending on how Node.js was installed or updated, that critical path entry can get left behind. My &lt;code&gt;echo %PATH%&lt;/code&gt; confirmed the directory where npm drops executables wasn't in the search list. Because the path was missing, Windows had no way to resolve &lt;code&gt;markdown-it-cli&lt;/code&gt; when I typed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Solution&lt;/strong&gt;&lt;br&gt;
Fixing this required updating the environment variables, but there's a trap that caught me out. I opened Windows Environment Variables and added &lt;code&gt;%AppData%\npm&lt;/code&gt; to the User PATH variable. This points directly to where &lt;code&gt;npm root -g&lt;/code&gt; indicated my tools lived, ensuring Windows would find the executable.&lt;/p&gt;

&lt;p&gt;I saved the change and immediately tried running &lt;code&gt;markdown-it-cli&lt;/code&gt; in my current terminal window. It still failed with the same error.&lt;/p&gt;

&lt;p&gt;The trap was that a running terminal holds the PATH snapshot from when it started. Updating environment variables doesn't retroactively fix an open session; the terminal process has already loaded its configuration. I had to close all terminal windows and open a brand new one for the OS changes to apply.&lt;/p&gt;

&lt;p&gt;Once I opened a fresh terminal, running &lt;code&gt;markdown-it -v&lt;/code&gt; worked perfectly. The command was recognized and executed as expected, confirming the fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson Learned&lt;/strong&gt;&lt;br&gt;
When a global CLI installs successfully but won't run, don't rush to reinstall or blame the package. Before doing anything drastic, follow this diagnostic path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;npm root -g&lt;/code&gt; to find where npm installed the tool.&lt;/li&gt;
&lt;li&gt;Check if that directory exists in your &lt;code&gt;%PATH%&lt;/code&gt; using &lt;code&gt;echo %PATH%&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the path is missing, add it to your User PATH and open a new terminal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fixing the environment variable saves you from wasting time on package reinstalls and gets your global tools working again. Always verify the path before assuming a broken install.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written from real hands-on experience, drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Local LLM vs Cloud API: My Mac Mini Cost Crossover</title>
      <dc:creator>DevLog</dc:creator>
      <pubDate>Thu, 27 Aug 2026 01:23:02 +0000</pubDate>
      <link>https://dev.to/devlog/local-llm-vs-cloud-api-my-mac-mini-cost-crossover-5eh4</link>
      <guid>https://dev.to/devlog/local-llm-vs-cloud-api-my-mac-mini-cost-crossover-5eh4</guid>
      <description>&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;br&gt;
I run an automated content pipeline (blog + YouTube Shorts) on a Mac mini with 48GB of unified memory. For months, my cloud LLM API (GLM) free tier handled everything comfortably at 60 RPM. Then, late last year, they quietly dropped the limit to 5–10 RPM overnight. My TTS pronunciation-QA batch script (&lt;code&gt;qa_shorts.zsh&lt;/code&gt; + &lt;code&gt;pron_map.py&lt;/code&gt;) started validating dozens of Shorts scripts and immediately hit the wall. The pipeline stalled for hours, waiting on retries while I watched tokens burn through my quota.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempts &amp;amp; Failures&lt;/strong&gt;&lt;br&gt;
The obvious fixes were throttling and retrying, but 5–10 RPM is brutal for bursty workloads. Backoff delays turned a short batch job into an hours-long crawl, and the queue behind it backed up every time. The bottleneck wasn't technical—it was the hard rate cap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root Cause&lt;/strong&gt;&lt;br&gt;
The real issue was a mismatch between my workload pattern and the pricing model. GLM charges per token, which works for steady, low-volume usage, but my pipeline is inherently bursty. When &lt;code&gt;qa_shorts.zsh&lt;/code&gt; fires off dozens of requests in a tight window, the 5–10 RPM limit turns a two-minute job into a four-hour stall. Meanwhile, my Mac mini is already running 24/7 for background tasks like video restoration and encoding. I was paying a premium for compute I already had sitting idle, just because the cloud provider decided to throttle my free tier. The crossover point isn’t about raw price; it’s about how your requests per minute and tokens per job interact with rate caps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Solution&lt;/strong&gt;&lt;br&gt;
I spun up LM Studio locally and loaded Qwen 35B-A3B (an uncensored "Heretic" fine-tune optimized for MLX, Apple’s native machine learning framework). I pointed it to the OpenAI-compatible endpoint at &lt;code&gt;localhost:1234&lt;/code&gt; and wired it directly into my pipeline. The crossover became clear immediately: for high-volume, repetitive tasks like pronunciation QA and bulk rewriting, the local model wins on every metric. Zero marginal cost (just electricity), no rate limits, and full privacy over my script drafts. For hard reasoning or long-context quality checks, I kept the cloud subscription model, which still offers a flat monthly fee with generous quotas. I implemented a simple fallback chain: cloud API first, local Qwen as the retry target during rate-limit storms. The stalls vanished, and the pipeline resumed its normal cadence without manual overrides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson Learned&lt;/strong&gt;&lt;br&gt;
Don’t trust vendor marketing when picking your LLM strategy. Measure the crossover with your own workload numbers—requests per minute, tokens per job, and batch distribution. In my case, it wasn’t price that forced the migration; it was rate limits. Once I mapped those numbers to my Mac mini’s 48GB unified memory and wired up a hybrid fallback, the pipeline ran smoother than ever.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written from real hands-on experience, drafted with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>macos</category>
    </item>
    <item>
      <title>My Local LLM Was Running at 1.6% of Its Context. Here's the Setting That Fixed It</title>
      <dc:creator>DevLog</dc:creator>
      <pubDate>Tue, 25 Aug 2026 09:06:37 +0000</pubDate>
      <link>https://dev.to/devlog/my-local-llm-was-running-at-16-of-its-context-heres-the-setting-that-fixed-it-3i4j</link>
      <guid>https://dev.to/devlog/my-local-llm-was-running-at-16-of-its-context-heres-the-setting-that-fixed-it-3i4j</guid>
      <description>&lt;p&gt;I run a content pipeline on a Mac mini (48GB unified memory) that splits long blog drafts into platform-specific short-form pieces. That job — read a 30-page document, hold the whole thing in mind, extract what matters for YouTube Shorts vs TikTok vs Reels — is exactly what long-context LLMs are supposed to be good at.&lt;/p&gt;

&lt;p&gt;Mine wasn't. It kept "forgetting" the second half of every document, dropping key details, and producing shallow summaries no matter how I tuned the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three weeks of tuning the wrong thing
&lt;/h2&gt;

&lt;p&gt;I did what you'd do. Simplified the prompt. Rewrote the template. Swapped models. Re-downloaded them, twice. Spent entire evenings after work on this, convinced the model was the problem — a Q4_K_M quantized 13B–20B model should handle long documents, right? The symptoms said otherwise: solid on the first pages, incoherent by the end.&lt;/p&gt;

&lt;p&gt;Classic context-window behavior. I just didn't see it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one line in the console
&lt;/h2&gt;

&lt;p&gt;Then I actually read the LM Studio load log instead of scrolling past it:&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;context_length&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model I was running supports &lt;strong&gt;262,144 tokens of context&lt;/strong&gt;. It was loaded with &lt;strong&gt;4,096&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's 1.6% of what the model can do. A 48-lane highway restricted to one lane — and every long document I fed it was quietly getting truncated into memory of just the opening section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happened
&lt;/h2&gt;

&lt;p&gt;LM Studio's just-in-time model loading picks a conservative default context length on first load. For chat and short Q&amp;amp;A, 4096 is plenty and keeps memory pressure low — a sensible default for most users. For document-scale work, it's a silent killer. Nothing errors out. Nothing warns you. The model just appears to have a bad memory.&lt;/p&gt;

&lt;p&gt;Context is the model's working memory. Cap it at 4k tokens and a 30-page brief becomes "read the first two pages, forget the rest."&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set context length explicitly on load.&lt;/strong&gt; In LM Studio's model settings, &lt;code&gt;Context Length: 260000&lt;/code&gt; (whatever your model supports — check the model card, not the default), then reload. On 48GB of unified memory the larger KV cache is entirely affordable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard against silent reloads.&lt;/strong&gt; I added a simple flag to the pipeline so an in-flight job blocks model reloads. The default resetting itself mid-workflow is how you get this bug back.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Immediate, dramatic improvement. Full-document comprehension, per-platform extraction without drift, details intact end to end. Same model, same hardware, same prompt — one setting was capping ~98% of the model's effective utility for my workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;Defaults are tuned for the average case, and document-scale synthesis is not the average case. When a local LLM "feels dumb," check what it was actually loaded with before blaming the weights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the real context limit on the model card&lt;/li&gt;
&lt;li&gt;Read the load log — what &lt;code&gt;context_length&lt;/code&gt; is actually in effect?&lt;/li&gt;
&lt;li&gt;Set it explicitly, every time, in your load scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most expensive performance bug I've shipped was a single default value.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is based on a first-hand work log, written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>lmstudio</category>
      <category>machinelearning</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Running Tailscale Without sudo: The Userspace-Networking Trade-offs Nobody Mentions</title>
      <dc:creator>DevLog</dc:creator>
      <pubDate>Mon, 24 Aug 2026 08:44:15 +0000</pubDate>
      <link>https://dev.to/devlog/running-tailscale-without-sudo-the-userspace-networking-trade-offs-nobody-mentions-16a3</link>
      <guid>https://dev.to/devlog/running-tailscale-without-sudo-the-userspace-networking-trade-offs-nobody-mentions-16a3</guid>
      <description>&lt;p&gt;Corporate policy: no admin rights on my work laptop. My problem: I needed to reach that machine — and SSH into my other machines — from a Galaxy Tab while away. Filing a VPN request with the security team would mean paperwork and a hard no for personal tooling. And without the sudo password, installing anything system-level is off the table.&lt;/p&gt;

&lt;p&gt;That's when Tailscale's &lt;code&gt;userspace-networking&lt;/code&gt; mode caught my eye: a VPN that runs entirely in user space. No root, no system service, no TUN driver. It sounded too good to be true, and in a few ways, it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install: the easy part
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;tailscale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI formula installs without admin rights. (The GUI app comes as a cask — which needs sudo, so that's out.) The real work starts with the daemon: normally &lt;code&gt;tailscaled&lt;/code&gt; registers as a system service, but in userspace mode you run it yourself:&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;nohup &lt;/span&gt;tailscaled &lt;span class="nt"&gt;--tun&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;userspace-networking &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--statedir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.tailscale &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--socket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.tailscale/tailscaled.sock &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail: point &lt;code&gt;statedir&lt;/code&gt; and &lt;code&gt;socket&lt;/code&gt; somewhere under &lt;code&gt;$HOME&lt;/code&gt;. Everything else about this setup flows from that one decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha #1: the socket path
&lt;/h2&gt;

&lt;p&gt;With the daemon running, I tried &lt;code&gt;tailscale status&lt;/code&gt; and got a "socket not found" error. The daemon was alive — I'd just started it. What happened?&lt;/p&gt;

&lt;p&gt;The CLI looks for the socket at the system default (&lt;code&gt;/var/run/tailscale/tailscaled.sock&lt;/code&gt;). In userspace mode it actually lives at &lt;code&gt;$HOME/.tailscale/tailscaled.sock&lt;/code&gt;, and the CLI won't guess. Every command needs the flag spelled out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tailscale &lt;span class="nt"&gt;--socket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.tailscale/tailscaled.sock status
tailscale &lt;span class="nt"&gt;--socket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.tailscale/tailscaled.sock ip
tailscale &lt;span class="nt"&gt;--socket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.tailscale/tailscaled.sock &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--ssh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typing that path five times a day gets old fast. A shell alias fixes it:&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;alias &lt;/span&gt;&lt;span class="nv"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'tailscale --socket=$HOME/.tailscale/tailscaled.sock'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wish I'd set that up on day one instead of week two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha #2: it's not a full VPN — it's a SOCKS5 proxy
&lt;/h2&gt;

&lt;p&gt;This is the one that cost me an evening. I turned on Tailscale's built-in SSH (&lt;code&gt;ts set --ssh&lt;/code&gt;), connected from the Tab to the laptop's tailnet IP — worked beautifully. Then I tried browsing an internal site from a browser and... nothing.&lt;/p&gt;

&lt;p&gt;Here's what's actually happening under &lt;code&gt;--tun=userspace-networking&lt;/code&gt;: it's &lt;strong&gt;not&lt;/strong&gt; a system-level VPN. Traffic moves through a &lt;strong&gt;SOCKS5 proxy&lt;/strong&gt;, which means apps don't route through Tailscale automatically. Anything that needs the tailnet has to be pointed at the proxy explicitly — browser proxy settings, &lt;code&gt;curl --socks5&lt;/code&gt;, and so on. A real VPN is a highway all traffic uses; this is a special pass one road accepts.&lt;/p&gt;

&lt;p&gt;So: inbound SSH to my machines, perfect. Arbitrary apps reaching the tailnet, manual configuration per app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha #3: reboots
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nohup&lt;/code&gt; keeps the daemon alive after you close the terminal, but it isn't registered with &lt;code&gt;launchd&lt;/code&gt;. Every reboot, the daemon is gone — and there's no systemd unit equivalent you can install without sudo. I lost a few mornings to "why won't it connect" before the muscle memory of re-running the launch command kicked in. A note in my shell rc file with the exact command helps more than you'd think.&lt;/p&gt;

&lt;p&gt;Also worth knowing: in userspace mode, the machine can accept &lt;strong&gt;inbound&lt;/strong&gt; connections but won't route its own outbound traffic through the tailnet by default. Testing "can I reach my own Tailscale IP" from the same machine times out — that's expected behavior, not a broken install.&lt;/p&gt;

&lt;h2&gt;
  
  
  What worked out of the box
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tailnet joined cleanly; the laptop picked up its 100.x.x.x IP immediately&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tailscale set --ssh&lt;/code&gt; gives you SSH without opening port 22 or touching macOS Remote Login settings — this alone justified the setup&lt;/li&gt;
&lt;li&gt;Connecting from the Galaxy Tab to the laptop over the tailnet: zero issues&lt;/li&gt;
&lt;li&gt;All state and logs live under &lt;code&gt;~/.tailscale/&lt;/code&gt;, easy to inspect&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest scorecard
&lt;/h2&gt;

&lt;p&gt;No-root constraint forces trade-offs, and this is what they look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Works:&lt;/strong&gt; inbound SSH via Tailscale's built-in server; brew install with no admin; SOCKS5 proxy for per-app access; survives terminal close (&lt;code&gt;nohup&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doesn't:&lt;/strong&gt; system-wide VPN routing; the GUI app (needs sudo); apps auto-routing through the tailnet; surviving a reboot (no launchd without root)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the mode name literally.&lt;/strong&gt; "Userspace networking" means userspace constraints: user-level socket paths, user-launched daemons, per-app proxies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alias the socket flag on day one.&lt;/strong&gt; Future-you will be grateful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Official guides assume the standard install.&lt;/strong&gt; Every doc that says "just run &lt;code&gt;tailscale status&lt;/code&gt;" silently assumes the system daemon. Under constraints, expect one layer of translation between the docs and your reality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you only need inbound SSH, this is nearly perfect.&lt;/strong&gt; The proxy awkwardness only bites when you want general outbound tailnet traffic.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;This post is based on a first-hand work log, written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>tailscale</category>
      <category>vpn</category>
      <category>networking</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
