<?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: VX</title>
    <description>The latest articles on DEV Community by VX (@vzn-vx).</description>
    <link>https://dev.to/vzn-vx</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%2F4172151%2F5cf8fc26-89c1-4591-8d21-8dd3f2f0fa46.png</url>
      <title>DEV Community: VX</title>
      <link>https://dev.to/vzn-vx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vzn-vx"/>
    <language>en</language>
    <item>
      <title>Branch caches a server enforces</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:19:30 +0000</pubDate>
      <link>https://dev.to/vzn-vx/branch-caches-a-server-enforces-5ejf</link>
      <guid>https://dev.to/vzn-vx/branch-caches-a-server-enforces-5ejf</guid>
      <description>&lt;p&gt;A pull request reuses everything &lt;code&gt;main&lt;/code&gt; built. Nothing it builds can&lt;br&gt;
land in &lt;code&gt;main&lt;/code&gt;'s cache, so one bad build cannot poison the next run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgcGFydGljaXBhbnQgUFIgYXMgUFIgcnVuIChmZWF0LXgpCiAgcGFydGljaXBhbnQgUyBhcyBjYWNoZSBzZXJ2ZXIKICBQUi0-PlM6IHJlYWQsIHNjb3BlczogZmVhdC14LCB0aGVuIG1haW4KICBTLS0-PlBSOiBmaXJzdCBoaXQKICBQUi0-PlM6IHdyaXRlLCB3aXRoIENJJ3Mgc2lnbmVkIE9JREMgdG9rZW4KICBTLT4-UzogdG9rZW4gc2F5cyBmZWF0LXgsIHN0b3JlIGluIGZlYXQteCBvbmx5Cg%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgcGFydGljaXBhbnQgUFIgYXMgUFIgcnVuIChmZWF0LXgpCiAgcGFydGljaXBhbnQgUyBhcyBjYWNoZSBzZXJ2ZXIKICBQUi0-PlM6IHJlYWQsIHNjb3BlczogZmVhdC14LCB0aGVuIG1haW4KICBTLS0-PlBSOiBmaXJzdCBoaXQKICBQUi0-PlM6IHdyaXRlLCB3aXRoIENJJ3Mgc2lnbmVkIE9JREMgdG9rZW4KICBTLT4-UzogdG9rZW4gc2F5cyBmZWF0LXgsIHN0b3JlIGluIGZlYXQteCBvbmx5Cg%3FbgColor%3Dwhite" alt="diagram" width="592" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;th&gt;Who makes it&lt;/th&gt;
&lt;th&gt;From what&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where a write lands&lt;/td&gt;
&lt;td&gt;the server&lt;/td&gt;
&lt;td&gt;the CI's signed OIDC token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Which branches a read sees&lt;/td&gt;
&lt;td&gt;the client&lt;/td&gt;
&lt;td&gt;one header, own branch first, then &lt;code&gt;main&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  The token decides writes
&lt;/h2&gt;

&lt;p&gt;The server checks the CI's signed OIDC token against its trust policy. A&lt;br&gt;
matching token writes the trusted cache. Any other token from the repo&lt;br&gt;
writes only its own branch. No token writes nothing.&lt;/p&gt;

&lt;p&gt;No header or key can change that.&lt;/p&gt;
&lt;h2&gt;
  
  
  One header steers reads
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;HEAD /v8/artifacts/0a1b2c3d4e5f6789
x-artifact-read-scopes: refs/heads/feat-x, refs/heads/main
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The server returns the first hit in that order. Keys are the same on&lt;br&gt;
every branch, so a hit on &lt;code&gt;main&lt;/code&gt; is the bytes &lt;code&gt;feat-x&lt;/code&gt; would have built.&lt;br&gt;
A server that ignores the header keeps one shared cache.&lt;/p&gt;

&lt;p&gt;CI refs come from env (&lt;code&gt;GITHUB_HEAD_REF&lt;/code&gt; or &lt;code&gt;GITHUB_REF&lt;/code&gt;, then&lt;br&gt;
&lt;code&gt;GITHUB_BASE_REF&lt;/code&gt;; &lt;code&gt;CI_COMMIT_REF_NAME&lt;/code&gt;, then&lt;br&gt;
&lt;code&gt;CI_MERGE_REQUEST_TARGET_BRANCH_NAME&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;On a laptop they come from git: the current branch, then &lt;code&gt;origin/HEAD&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two client modes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;scope&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Read request&lt;/th&gt;
&lt;th&gt;For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;'header'&lt;/code&gt; (default)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hash(inputs)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;plain key + &lt;code&gt;x-artifact-read-scopes&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;servers that implement the rules below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'key'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hash(inputs + ref)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;one batch with each ref's key, first hit wins&lt;/td&gt;
&lt;td&gt;servers that ignore the header (Vercel)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vx/workspace.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;turboCache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx-migrate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;turboCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt; &lt;span class="c1"&gt;// a server that ignores the header&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vx/workspace.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;turboCache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx-migrate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;turboCache&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt; &lt;span class="c1"&gt;// a server that ignores the header&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;'key'&lt;/code&gt; separates branches but secures nothing: anyone can compute&lt;br&gt;
&lt;code&gt;main&lt;/code&gt;'s key. Only a server can enforce, so &lt;code&gt;'header'&lt;/code&gt; is the default.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nxCache()&lt;/code&gt; sends plain requests, since the Nx spec has no batch query.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building the server half
&lt;/h2&gt;

&lt;p&gt;Everything below is the Turbo artifacts API (&lt;code&gt;/v8/artifacts&lt;/code&gt;) plus one&lt;br&gt;
header.&lt;/p&gt;
&lt;h3&gt;
  
  
  Server config
&lt;/h3&gt;

&lt;p&gt;vx's own &lt;a href="https://vznjs.github.io/vx/docs/servers/cache/" rel="noopener noreferrer"&gt;cache server&lt;/a&gt; does this over its&lt;br&gt;
&lt;code&gt;/v1&lt;/code&gt; wire. For a server on the Turbo wire, here is an example: a trust&lt;br&gt;
policy per team.&lt;/p&gt;

&lt;p&gt;A rule names token claims; each value is an exact string, a glob, or a&lt;br&gt;
list.&lt;/p&gt;

&lt;p&gt;A token matches a rule when every claim in it matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;trust&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// GitHub: main and merge queue, only from the main-branch workflow&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;iss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://token.actions.githubusercontent.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;repository_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;123456789&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;event_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;merge_group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// pull_request_target and issue_comment run PR code with ref=main&lt;/span&gt;
      &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refs/heads/main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refs/heads/gh-readonly-queue/main/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;ref_protected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// an unprotected branch can be pushed with unreviewed code&lt;/span&gt;
      &lt;span class="na"&gt;job_workflow_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;org/repo/.github/workflows/ci.yml@refs/heads/main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// GitLab: protected main, on our own runners&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;iss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://gitlab.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4567&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ref_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;refs/heads/main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ref_protected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;pipeline_source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schedule&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// a manual pipeline can inject CI variables&lt;/span&gt;
      &lt;span class="na"&gt;runner_environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;self-hosted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pin &lt;code&gt;repository_id&lt;/code&gt; or &lt;code&gt;project_id&lt;/code&gt;, not the name: after a rename or&lt;br&gt;
delete, another repository can take the name, never the id.&lt;/p&gt;

&lt;p&gt;Trusted is &lt;code&gt;main&lt;/code&gt; and its merge queue only: anyone who can push can tag&lt;br&gt;
any commit, so a tag's run writes to its own scope, as a branch does.&lt;/p&gt;
&lt;h3&gt;
  
  
  Write scope
&lt;/h3&gt;

&lt;p&gt;Built into the server, not configurable. A token that matches no rule&lt;br&gt;
writes to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;refs/heads/&amp;lt;head_ref&amp;gt;&lt;/code&gt; on a PR run, otherwise &lt;code&gt;ref&lt;/code&gt;. A PR
run writes to its head branch, so a laptop finds it from git alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitLab:&lt;/strong&gt; &lt;code&gt;ref_path&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Rules
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Auth.&lt;/strong&gt; Accept GitHub and GitLab OIDC tokens as &lt;code&gt;Authorization:
Bearer&lt;/code&gt;, and verify the signature (the issuer's JWKS), &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;aud&lt;/code&gt;
and &lt;code&gt;exp&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every read needs a token. Each developer creates their own read-only&lt;br&gt;
   token, and a shared read-only token may serve other CIs; neither ever&lt;br&gt;
   writes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audience.&lt;/strong&gt; &lt;code&gt;aud&lt;/code&gt; must be this server's own, so a token minted for
another service can't be replayed here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust match.&lt;/strong&gt; As above: every claim a rule names must match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write&lt;/strong&gt; (&lt;code&gt;PUT /v8/artifacts/:hash&lt;/code&gt;). A token that matches a rule
stores to trusted. Any other token stores to its write scope. A token
from a repository or project no rule names writes nothing. Entries
are immutable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read&lt;/strong&gt; (&lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD /v8/artifacts/:hash&lt;/code&gt;, and the batch
&lt;code&gt;POST /v8/artifacts&lt;/code&gt;). For each ref in the header, in order: if it
matches a rule's ref glob, look in trusted; otherwise look in that
ref's scope. Return the first hit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No header.&lt;/strong&gt; Read from trusted only, so plain Turbo clients keep
working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per repository.&lt;/strong&gt; Every scope, trusted included, lives in that
repository's own bucket, taken from the token (&lt;code&gt;repository_id&lt;/code&gt;,
&lt;code&gt;project_id&lt;/code&gt;, or the read token's repository). One team's repositories
never share a branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header limits.&lt;/strong&gt; Each entry must be a &lt;code&gt;refs/...&lt;/code&gt; string; dedupe the
list and cap it at 8 refs. vx sends at most 8.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge queue.&lt;/strong&gt; It writes to trusted before its code lands, so a
queued PR that drops out leaves trusted entries behind. That is
accepted: queue code is approved code.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Token holds&lt;/th&gt;
&lt;th&gt;&lt;code&gt;x-artifact-read-scopes&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Server reads (first hit)&lt;/th&gt;
&lt;th&gt;Server writes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;main push&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ref=refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;merge queue&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ref=refs/heads/gh-readonly-queue/main/…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/gh-readonly-queue/main/…, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR #12 (feature→main)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ref=refs/pull/12/merge&lt;/code&gt;, &lt;code&gt;head_ref=feature&lt;/code&gt;, &lt;code&gt;base_ref=main&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;feature, trusted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;branch push&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ref=refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;feature, trusted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fork PR&lt;/td&gt;
&lt;td&gt;no token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/&amp;lt;head&amp;gt;, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;nothing (no token)&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;local on main&lt;/td&gt;
&lt;td&gt;personal token (read-only)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;local on feature&lt;/td&gt;
&lt;td&gt;personal token (read-only)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;feature, trusted&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  GitLab
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Token holds&lt;/th&gt;
&lt;th&gt;&lt;code&gt;x-artifact-read-scopes&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Server reads (first hit)&lt;/th&gt;
&lt;th&gt;Server writes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;main pipeline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ref_path=refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;merge train&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ref_path=refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;feature, trusted&lt;/td&gt;
&lt;td&gt;nothing (client read-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MR or merged results (feature→main)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ref_path=refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;feature, trusted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;branch pipeline&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ref_path=refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;feature, trusted&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fork MR&lt;/td&gt;
&lt;td&gt;fork project's token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;nothing (wrong project)&lt;/td&gt;
&lt;td&gt;nothing (wrong project)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;local on main&lt;/td&gt;
&lt;td&gt;personal token (read-only)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;trusted&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;local on feature&lt;/td&gt;
&lt;td&gt;personal token (read-only)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;refs/heads/feature, refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;feature, trusted&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A GitLab token has no merge-train claim, so the server would store a&lt;br&gt;
train's results in the source branch's scope.&lt;/p&gt;

&lt;p&gt;Those results are built from the branch merged with main and the MRs&lt;br&gt;
ahead of it, so the branch almost never reuses them. The CI knows it is&lt;br&gt;
a train, so turn writes off there:&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;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;VX_CACHE_POLICY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local:rw,remote:r&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on_success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stacked PR (feature-b→feature) reads &lt;code&gt;feature-b, feature, main&lt;/code&gt; in CI&lt;br&gt;
and &lt;code&gt;feature-b, main&lt;/code&gt; on a laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the client sends
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Task keys reach every remote plain.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;turboCache()&lt;/code&gt; sends &lt;code&gt;x-artifact-read-scopes&lt;/code&gt; on every read; a &lt;code&gt;PUT&lt;/code&gt;
names no scope.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;VX_CACHE_SCOPE&lt;/code&gt;, &lt;code&gt;github({ cacheScope })&lt;/code&gt; and &lt;code&gt;gitlab({ cacheScope })&lt;/code&gt;
scope only what plugins store themselves (&lt;code&gt;ctx.cache&lt;/code&gt;, run records).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The remote cache page's &lt;a href="https://vznjs.github.io/vx/docs/remote-cache/#cache-scopes-and-tokens" rel="noopener noreferrer"&gt;cache scopes and tokens&lt;/a&gt; has the setup.&lt;br&gt;
More at &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/branch-caches-a-server-enforces/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>When running beats downloading</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:18:38 +0000</pubDate>
      <link>https://dev.to/vzn-vx/when-running-beats-downloading-2n60</link>
      <guid>https://dev.to/vzn-vx/when-running-beats-downloading-2n60</guid>
      <description>&lt;p&gt;If a download would take longer than running the task, vx runs the task.&lt;br&gt;
Here 500 tasks are remote hits, and 20 of them are 8 MB on a 4 MB/s&lt;br&gt;
link:&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="nv"&gt;$ &lt;/span&gt;vx run build &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--dry&lt;/span&gt;
500 task&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; planned, 500 cache hits &lt;span class="o"&gt;(&lt;/span&gt;500 remote, 153 MB&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;vx run build &lt;span class="nt"&gt;--all&lt;/span&gt;
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;20↑ · remote 480↓ 129 KB
  &lt;span class="nb"&gt;time      &lt;/span&gt;1.02s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those 20 would take 2 s each to download. Their tasks run in&lt;br&gt;
milliseconds, so vx ran them here (&lt;code&gt;local 20↑&lt;/code&gt;) and downloaded the&lt;br&gt;
other 480.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The remote says&lt;/th&gt;
&lt;th&gt;Full run&lt;/th&gt;
&lt;th&gt;Downloaded&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;size and task time&lt;/td&gt;
&lt;td&gt;1.15 s&lt;/td&gt;
&lt;td&gt;129 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;size only&lt;/td&gt;
&lt;td&gt;3.12 s&lt;/td&gt;
&lt;td&gt;153 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;small&gt;Min of 3 interleaved runs, empty local cache · &lt;code&gt;private/vx-bench/generate.ts&lt;/code&gt;, 500 projects, 20 writing 8 MB · Turborepo-protocol stub 30 ms away at 4 MB/s, &lt;code&gt;turboCache()&lt;/code&gt; · Linux x64, 4 cores · vx 22f6eb7 · 2026-10-23&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Weigh the download against the task
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgRFtkb3dubG9hZCBzdGFydHNdIC0tPiBQeyJldmVyeSAyNSBtczo8YnIvPnRpbWUgbGVmdCA-IDIgw5cgdGFzayB0aW1lPyJ9CiAgUCAtLT58bm98IFAKICBQIC0tPnx5ZXN8IFJbZ2l2ZSB1cCwgcnVuIHRoZSB0YXNrIGhlcmVdCiAgUCAtLT58ZG9uZXwgSFtyZXN0b3JlIHRoZSBoaXRdCg%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgRFtkb3dubG9hZCBzdGFydHNdIC0tPiBQeyJldmVyeSAyNSBtczo8YnIvPnRpbWUgbGVmdCA-IDIgw5cgdGFzayB0aW1lPyJ9CiAgUCAtLT58bm98IFAKICBQIC0tPnx5ZXN8IFJbZ2l2ZSB1cCwgcnVuIHRoZSB0YXNrIGhlcmVdCiAgUCAtLT58ZG9uZXwgSFtyZXN0b3JlIHRoZSBoaXRdCg%3FbgColor%3Dwhite" alt="diagram" width="884" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The remote has to tell vx the entry's size and its task's time. Turbo's&lt;br&gt;
cache API sends both (&lt;code&gt;Content-Length&lt;/code&gt;, &lt;code&gt;x-artifact-duration&lt;/code&gt;), and&lt;br&gt;
&lt;code&gt;turboCache()&lt;/code&gt; passes them on.&lt;/p&gt;

&lt;p&gt;Twice the task time, because a run here also saves its outputs. The task&lt;br&gt;
counts as a miss and uploads nothing.&lt;/p&gt;

&lt;p&gt;A remote that doesn't send the time is always waited for, as in the&lt;br&gt;
second row. vx won't guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know what a run will pull
&lt;/h2&gt;

&lt;p&gt;The footer shows the bytes each way. &lt;code&gt;--dry&lt;/code&gt; shows what the remote hits&lt;br&gt;
would download, before anything runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  For plugin authors
&lt;/h2&gt;

&lt;p&gt;A remote's &lt;code&gt;has&lt;/code&gt; returns &lt;code&gt;{ size, durationMs }&lt;/code&gt;, either one, &lt;code&gt;{}&lt;/code&gt;, or&lt;br&gt;
&lt;code&gt;null&lt;/code&gt; for a missing key. &lt;code&gt;hasMany&lt;/code&gt; returns a &lt;code&gt;Map&lt;/code&gt; of the same. Send a&lt;br&gt;
size and you get the &lt;code&gt;--dry&lt;/code&gt; figure.&lt;/p&gt;

&lt;p&gt;Send a duration too and you get the slow-download check. &lt;a href="https://vznjs.github.io/vx/docs/writing-plugins/#your-own-cache" rel="noopener noreferrer"&gt;Your own&lt;br&gt;
cache&lt;/a&gt; shows a whole remote.&lt;/p&gt;

&lt;p&gt;Setup: &lt;a href="https://vznjs.github.io/vx/docs/remote-cache/" rel="noopener noreferrer"&gt;Remote cache&lt;/a&gt;. More at&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/when-running-beats-downloading/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Every task has a key</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:18:37 +0000</pubDate>
      <link>https://dev.to/vzn-vx/every-task-has-a-key-now-26c4</link>
      <guid>https://dev.to/vzn-vx/every-task-has-a-key-now-26c4</guid>
      <description>&lt;p&gt;A task you don't cache still has a key. Change a code generator's env,&lt;br&gt;
and the cached build after it runs again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineProject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineProject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;codegen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mkdir -p gen &amp;amp;&amp;amp; echo "export const api = &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;"$API_URL&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;"" &amp;gt; gen/api.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;passThrough&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;schema.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.env.local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;API_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;codegen&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mkdir -p dist &amp;amp;&amp;amp; cat gen/api.ts &amp;gt; dist/app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist/**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://a.example vx run packages/e#build
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1✓ 1∅

&lt;span class="nv"&gt;$ API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://b.example vx run packages/e#build
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1∅ 1↑
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;packages/e/dist/app.js
&lt;span class="nb"&gt;export &lt;/span&gt;const api &lt;span class="o"&gt;=&lt;/span&gt; https://b.example

&lt;span class="nv"&gt;$ &lt;/span&gt;vx why packages/e#build
  verdict    cache key changed: upstream packages/e#codegen
  what changed &lt;span class="o"&gt;(&lt;/span&gt;1 component, 3 unchanged&lt;span class="o"&gt;)&lt;/span&gt;:
    changed upstream  packages/e#codegen  35dfe1115aedc6d6 → a7a53dc4d9e0bde9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;codegen&lt;/code&gt; never caches (&lt;code&gt;∅&lt;/code&gt;), but its key goes into &lt;code&gt;build&lt;/code&gt;'s key. A&lt;br&gt;
new &lt;code&gt;API_URL&lt;/code&gt; builds again instead of restoring a &lt;code&gt;dist/&lt;/code&gt; made for the&lt;br&gt;
old one.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Demo workspace · vx 69617eb · 2026-10-23&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgRVsiQVBJX1VSTCwgc2NoZW1hLmpzb24sIC5lbnYubG9jYWwiXSAtLT4gQ1siY29kZWdlbiBrZXk8YnIvPm5ldmVyIGNhY2hlZCJdCiAgQyAtLT4gQlsiYnVpbGQga2V5Il0KICBTWyJzcmMvKioiXSAtLT4gQgogIEIgLS0-IEh7aW4gY2FjaGU_fQogIEggLS0-fHllc3wgUltyZXN0b3JlIGRpc3QvXQogIEggLS0-fG5vfCBYW3J1biBidWlsZF0K%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgRVsiQVBJX1VSTCwgc2NoZW1hLmpzb24sIC5lbnYubG9jYWwiXSAtLT4gQ1siY29kZWdlbiBrZXk8YnIvPm5ldmVyIGNhY2hlZCJdCiAgQyAtLT4gQlsiYnVpbGQga2V5Il0KICBTWyJzcmMvKioiXSAtLT4gQgogIEIgLS0-IEh7aW4gY2FjaGU_fQogIEggLS0-fHllc3wgUltyZXN0b3JlIGRpc3QvXQogIEggLS0-fG5vfCBYW3J1biBidWlsZF0K%3FbgColor%3Dwhite" alt="diagram" width="1046" height="198"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Inputs live on the task
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;inputs&lt;/code&gt; sits on the task, next to &lt;code&gt;exec&lt;/code&gt;. It sets the key whether the&lt;br&gt;
task caches or not, as in Turbo and Nx. &lt;code&gt;cache&lt;/code&gt; holds only &lt;code&gt;outputs&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;inputStability&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A task with a command and no &lt;code&gt;inputs&lt;/code&gt; has no key. vx won't guess one.&lt;br&gt;
Every task after it runs again and saves nothing:&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="nv"&gt;$ &lt;/span&gt;vx run packages/e#build      &lt;span class="c"&gt;# codegen without inputs&lt;/span&gt;
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1∅ · 1 not saved
&lt;span class="nv"&gt;$ &lt;/span&gt;vx run packages/e#build
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1∅ · 1 not saved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A missing input costs a re-run, never a wrong cache hit. Every rule is&lt;br&gt;
on the &lt;a href="https://vznjs.github.io/vx/docs/caching/" rel="noopener noreferrer"&gt;Caching&lt;/a&gt; page.&lt;/p&gt;
&lt;h2&gt;
  
  
  A gitignored file can still be an input
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.env.local&lt;/code&gt; is gitignored. Editing it still changes the key:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"TOKEN=2"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; packages/e/.env.local
&lt;span class="nv"&gt;$ &lt;/span&gt;vx run packages/e#build
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1∅ 1↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vx reads tracked files' hashes from git. A path you name outright&lt;br&gt;
(&lt;code&gt;.env.local&lt;/code&gt;), or a glob whose fixed part git ignores (&lt;code&gt;gen/**&lt;/code&gt;), is&lt;br&gt;
read from disk.&lt;/p&gt;

&lt;p&gt;A broad glob like &lt;code&gt;src/**&lt;/code&gt; still skips ignored files, and &lt;code&gt;node_modules&lt;/code&gt;&lt;br&gt;
is never walked.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--affected&lt;/code&gt; sees only what git sees, as in Nx. An ignored file never&lt;br&gt;
picks a task by itself, but the key still changes when the task runs.&lt;/p&gt;

&lt;p&gt;The full key, part by part: &lt;a href="https://vznjs.github.io/vx/caching/" rel="noopener noreferrer"&gt;Caching&lt;/a&gt;. More at&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/every-task-has-a-key/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What a run does not do</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Tue, 20 Oct 2026 22:32:30 +0000</pubDate>
      <link>https://dev.to/vzn-vx/three-things-a-run-stopped-doing-2lf7</link>
      <guid>https://dev.to/vzn-vx/three-things-a-run-stopped-doing-2lf7</guid>
      <description>&lt;p&gt;A warm run is fast mostly because of work it skips. Here is what a vx&lt;br&gt;
run never does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgUlt2eCBydW5dIC0tPiBUW3J1biB0YXNrc10gLS0-IEZbcHJpbnQgZm9vdGVyXSAtLT4gRVtleGl0XQogIFIgLS4teCBDW2NsZWFuIG9sZCBjYWNoZV0KICBSIC0uLXggV1t3YWxrIHRoZSBzaGFyZWQgc3RvcmVdCiAgRiAtLi14IFNbInRpbWUgc2F2ZWQiIGd1ZXNzXQo%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgUlt2eCBydW5dIC0tPiBUW3J1biB0YXNrc10gLS0-IEZbcHJpbnQgZm9vdGVyXSAtLT4gRVtleGl0XQogIFIgLS4teCBDW2NsZWFuIG9sZCBjYWNoZV0KICBSIC0uLXggV1t3YWxrIHRoZSBzaGFyZWQgc3RvcmVdCiAgRiAtLi14IFNbInRpbWUgc2F2ZWQiIGd1ZXNzXQo%3FbgColor%3Dwhite" alt="diagram" width="803" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup runs when you choose
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vx cache clean &lt;span class="nt"&gt;--older-than&lt;/span&gt; 30d &lt;span class="nt"&gt;--max-size&lt;/span&gt; 10G
› vx cache clean  nothing to free

&lt;span class="nv"&gt;$ &lt;/span&gt;vx cache clean &lt;span class="nt"&gt;--max-size&lt;/span&gt; 100K
✓ vx cache clean  freed 94 MB

  999 entries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cleanup at the end of every run would be seven &lt;code&gt;DELETE&lt;/code&gt;s, four of them&lt;br&gt;
full scans, and it grows with history. Run &lt;code&gt;vx cache clean&lt;/code&gt; from cron or&lt;br&gt;
a scheduled CI job instead.&lt;/p&gt;

&lt;p&gt;There is no &lt;code&gt;cache.retention&lt;/code&gt; setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shared store opens by its index
&lt;/h2&gt;

&lt;p&gt;The store under &lt;code&gt;~/.vx&lt;/code&gt; opens by its index file. A run never lists its&lt;br&gt;
entries or checks their folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  The footer shows only what it measured
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  [2mtime    [0m  63ms [2m·[0m [2mmax 8ms[0m [2m·[0m [2mavg 6ms[0m [2m·[0m [2mmin 4ms[0m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is wall time, then the slowest, average and fastest task. Adding&lt;br&gt;
up the stored time of every cache hit would overstate the saving.&lt;br&gt;
Tasks that would have run side by side cost the clock only once.&lt;/p&gt;

&lt;p&gt;What each run does, stage by stage: &lt;a href="https://vznjs.github.io/vx/architecture/" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt;.&lt;br&gt;
More at &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/what-a-run-stopped-doing/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Brackets in a glob mean brackets</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Tue, 20 Oct 2026 22:31:49 +0000</pubDate>
      <link>https://dev.to/vzn-vx/we-took-back-a-glob-shortcut-2a3e</link>
      <guid>https://dev.to/vzn-vx/we-took-back-a-glob-shortcut-2a3e</guid>
      <description>&lt;p&gt;In a vx glob, &lt;code&gt;[jt]&lt;/code&gt; matches one character, &lt;code&gt;j&lt;/code&gt; or &lt;code&gt;t&lt;/code&gt;. A Next.js route&lt;br&gt;
folder like &lt;code&gt;app/[id]&lt;/code&gt; needs escaping.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Glob&lt;/th&gt;
&lt;th&gt;Matches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;src/*.[jt]s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.js&lt;/code&gt; and &lt;code&gt;.ts&lt;/code&gt; files in &lt;code&gt;src&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app/[id]/**&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;app/i/**&lt;/code&gt;, &lt;code&gt;app/d/**&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app/\[id\]/**&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;app/[id]/&lt;/code&gt; folder&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Turbo, Nx and shells read brackets the same way. A glob you copy from&lt;br&gt;
&lt;code&gt;turbo.json&lt;/code&gt; or an Nx project means the same thing in &lt;code&gt;vx.config.ts&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  See it in a run
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ls src app/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/*.[jt]s&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;[id&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;]/**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The double backslash is the escape inside a TypeScript string.&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"// e"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; app/i/x.ts          &lt;span class="c"&gt;# not an input&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;vx run packages/c#route
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1✓

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"// e"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'app/[id]/page.ts'&lt;/span&gt;  &lt;span class="c"&gt;# the escaped route&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;vx run packages/c#route
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1↑

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"// e"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; src/j.js            &lt;span class="c"&gt;# matched by [jt]&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;vx run packages/c#route
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1↑
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;Linux x64 · vx 69617eb · 2026-10-23&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;An edit to an input runs the task again (&lt;code&gt;1↑&lt;/code&gt;). Any other edit is a&lt;br&gt;
cache hit (&lt;code&gt;1✓&lt;/code&gt;). &lt;code&gt;@vzn/vx-migrate&lt;/code&gt; keeps Turbo's and Nx's brackets as&lt;br&gt;
they are, and escapes brackets in any path it writes.&lt;/p&gt;

&lt;p&gt;Glob rules: &lt;a href="https://vznjs.github.io/vx/caching/" rel="noopener noreferrer"&gt;Caching&lt;/a&gt;. More at&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/brackets-mean-brackets/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Cache transfers wait on the network</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Tue, 20 Oct 2026 22:30:57 +0000</pubDate>
      <link>https://dev.to/vzn-vx/cache-transfers-wait-on-the-network-3i6a</link>
      <guid>https://dev.to/vzn-vx/cache-transfers-wait-on-the-network-3i6a</guid>
      <description>&lt;p&gt;Cache uploads and downloads mostly wait on the network, so vx runs up&lt;br&gt;
to 64 at once even on a 4-core machine. 1,000 uploads to a cache 30 ms&lt;br&gt;
away take 2.5 s. With only 4 at once:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;vx run build &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--cache-concurrency-upload&lt;/span&gt; 4
  cache     remote 1000↑ 269 KB
  &lt;span class="nb"&gt;time      &lt;/span&gt;2.27s
real    0m8.345s

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;time &lt;/span&gt;vx run build &lt;span class="nt"&gt;--all&lt;/span&gt;
  cache     remote 1000↑ 269 KB
  &lt;span class="nb"&gt;time      &lt;/span&gt;2.38s
real    0m2.649s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The run's &lt;code&gt;time&lt;/code&gt; ends with its last task. Uploads finish in the&lt;br&gt;
background, and the process exits when they are done.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lane limit&lt;/th&gt;
&lt;th&gt;Upload all 1,000&lt;/th&gt;
&lt;th&gt;Restore all 1,000 from the remote&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;31.6 s&lt;/td&gt;
&lt;td&gt;9.0 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8.4 s&lt;/td&gt;
&lt;td&gt;8.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2.7 s&lt;/td&gt;
&lt;td&gt;2.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;default (64)&lt;/td&gt;
&lt;td&gt;2.5 s&lt;/td&gt;
&lt;td&gt;1.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;small&gt;Min of 3 interleaved runs · &lt;code&gt;private/vx-bench/generate.ts&lt;/code&gt;, 1,000 projects, &lt;code&gt;build --all&lt;/code&gt; · Turborepo-protocol cache that answers after 30 ms, &lt;code&gt;turboCache()&lt;/code&gt; · &lt;code&gt;CI=1&lt;/code&gt; · Linux x64, 4 cores · vx a1786b3 · 2026-10-23&lt;/small&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Three lanes, three limits
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lane&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;download&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;min(96, max(cores × 3, 64))&lt;/td&gt;
&lt;td&gt;mostly waiting on the network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;min(96, max(cores × 3, 64)), per remote&lt;/td&gt;
&lt;td&gt;the same, and it runs in the background&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;restore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;twice &lt;code&gt;run.concurrency&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;local disk work, closer to a task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The network formula is pnpm's: at least 64 at once, at most 96. If your&lt;br&gt;
server wants fewer connections, set fewer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vx/workspace.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vx run build &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--cache-concurrency-upload&lt;/span&gt; 16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A missing key costs no request
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgcGFydGljaXBhbnQgUiBhcyB2eCBydW4KICBwYXJ0aWNpcGFudCBDIGFzIHJlbW90ZSBjYWNoZQogIFItPj5DOiB3aGljaCBvZiB0aGVzZSBrZXlzIGV4aXN0PyAob25lIGJhdGNoKQogIEMtLT4-UjogdGhlIG9uZXMgaXQgaG9sZHMKICBSLT4-Qzogb25lIGRvd25sb2FkIHBlciBrZXkgaXQgaG9sZHMKICBOb3RlIG92ZXIgUjogZXZlcnkgb3RoZXIgdGFzayBydW5zLCBubyBtb3JlIHJlcXVlc3RzCg%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fc2VxdWVuY2VEaWFncmFtCiAgcGFydGljaXBhbnQgUiBhcyB2eCBydW4KICBwYXJ0aWNpcGFudCBDIGFzIHJlbW90ZSBjYWNoZQogIFItPj5DOiB3aGljaCBvZiB0aGVzZSBrZXlzIGV4aXN0PyAob25lIGJhdGNoKQogIEMtLT4-UjogdGhlIG9uZXMgaXQgaG9sZHMKICBSLT4-Qzogb25lIGRvd25sb2FkIHBlciBrZXkgaXQgaG9sZHMKICBOb3RlIG92ZXIgUjogZXZlcnkgb3RoZXIgdGFzayBydW5zLCBubyBtb3JlIHJlcXVlc3RzCg%3FbgColor%3Dwhite" alt="diagram" width="625" height="354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before tasks start, vx asks each remote in one batch which keys exist.&lt;br&gt;
A missing key costs nothing more. A key it has costs one download.&lt;/p&gt;

&lt;h2&gt;
  
  
  One request per plugin read
&lt;/h2&gt;

&lt;p&gt;Plugins keep entries in the same cache, like the timings&lt;br&gt;
&lt;code&gt;@vzn/vx-history&lt;/code&gt; learns. &lt;code&gt;ctx.cache.get&lt;/code&gt;, &lt;code&gt;has&lt;/code&gt; and &lt;code&gt;hasMany&lt;/code&gt;&lt;br&gt;
each make one request.&lt;/p&gt;

&lt;p&gt;Settings: &lt;a href="https://vznjs.github.io/vx/schema/" rel="noopener noreferrer"&gt;schema&lt;/a&gt;. Flags: &lt;a href="https://vznjs.github.io/vx/cli/" rel="noopener noreferrer"&gt;CLI reference&lt;/a&gt;.&lt;br&gt;
More at &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/waiting-on-the-network/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A cache hit is a file copy</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Tue, 20 Oct 2026 22:30:55 +0000</pubDate>
      <link>https://dev.to/vzn-vx/a-cache-hit-is-a-file-copy-1a8f</link>
      <guid>https://dev.to/vzn-vx/a-cache-hit-is-a-file-copy-1a8f</guid>
      <description>&lt;p&gt;A local cache hit is a file copy the kernel does. 1,000 hits of 96 KiB&lt;br&gt;
restore in 411 ms. With &lt;code&gt;dist&lt;/code&gt; deleted:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; packages/&lt;span class="k"&gt;*&lt;/span&gt;/dist &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;time &lt;/span&gt;vx run build &lt;span class="nt"&gt;--all&lt;/span&gt;
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1000↓
  &lt;span class="nb"&gt;time      &lt;/span&gt;214ms
real    0m0.411s

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; packages/&lt;span class="k"&gt;*&lt;/span&gt;/dist &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;time &lt;/span&gt;&lt;span class="nv"&gt;VX_CACHE_STORE_DECOMPRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;vx run build &lt;span class="nt"&gt;--all&lt;/span&gt;
  cache     &lt;span class="nb"&gt;local &lt;/span&gt;1000↓
  &lt;span class="nb"&gt;time      &lt;/span&gt;618ms
real    0m0.686s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same entries, two ways to keep them on disk:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Store (&lt;code&gt;cache.store.decompress&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Restore all 1,000&lt;/th&gt;
&lt;th&gt;Store on disk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;plain files (&lt;code&gt;true&lt;/code&gt;, default)&lt;/td&gt;
&lt;td&gt;411 ms&lt;/td&gt;
&lt;td&gt;111 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;one &lt;code&gt;tar.zst&lt;/code&gt; each (&lt;code&gt;false&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;686 ms&lt;/td&gt;
&lt;td&gt;99 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;small&gt;Min of 5 restores, 3 interleaved arms, fresh store per arm · &lt;code&gt;private/vx-bench/generate.ts&lt;/code&gt;, 1,000 projects, 96 KiB random output each · Linux x64, 4 cores · vx a1786b3 · 2026-10-23&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgU1t0YXNrIHJ1bnNdIC0tPnxzYXZlOiBjb3B5IG91dHB1dHMgb25jZXwgVFsoZW50cnkgYXMgcGxhaW4gZmlsZXMpXQogIFJbKHJlbW90ZSldIC0tPnxkb3dubG9hZDogZGVjb21wcmVzcyBvbmNlfCBUCiAgVCAtLT58aGl0OiBrZXJuZWwgY29weSwgMSwwMDAgaW4gNDExIG1zfCBEW2Rpc3QvXQogIFQgLS0-fHVwbG9hZDogcGFjayB0YXIuenN0fCBSCiAgc3R5bGUgVCBzdHJva2U6I2M2Zjg0ZSxzdHJva2Utd2lkdGg6MnB4Cg%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgU1t0YXNrIHJ1bnNdIC0tPnxzYXZlOiBjb3B5IG91dHB1dHMgb25jZXwgVFsoZW50cnkgYXMgcGxhaW4gZmlsZXMpXQogIFJbKHJlbW90ZSldIC0tPnxkb3dubG9hZDogZGVjb21wcmVzcyBvbmNlfCBUCiAgVCAtLT58aGl0OiBrZXJuZWwgY29weSwgMSwwMDAgaW4gNDExIG1zfCBEW2Rpc3QvXQogIFQgLS0-fHVwbG9hZDogcGFjayB0YXIuenN0fCBSCiAgc3R5bGUgVCBzdHJva2U6I2M2Zjg0ZSxzdHJva2Utd2lkdGg6MnB4Cg%3FbgColor%3Dwhite" alt="diagram" width="846" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The kernel does the copy
&lt;/h2&gt;

&lt;p&gt;Archives exist only on the network. Locally, each entry is stored as&lt;br&gt;
plain files, unpacked once.&lt;/p&gt;

&lt;p&gt;A hit hands each file to the kernel: &lt;code&gt;copy_file_range&lt;/code&gt; on Linux, or a&lt;br&gt;
clone on APFS, Btrfs or XFS. A clone shares blocks until one side&lt;br&gt;
writes, so a big output restores without copying bytes.&lt;/p&gt;

&lt;p&gt;vx never hard-links. A hard link would be faster, but then editing&lt;br&gt;
&lt;code&gt;dist/index.js&lt;/code&gt; in place would edit the cache too, and the next hit&lt;br&gt;
would restore your edit. A copy or clone keeps the cache safe.&lt;/p&gt;
&lt;h2&gt;
  
  
  Small entries stay in the index
&lt;/h2&gt;

&lt;p&gt;A thousand tiny outputs as a thousand folders is more work than the&lt;br&gt;
bytes are worth. So an entry up to 32 KiB stays in the SQLite index as&lt;br&gt;
one compressed row. You can change the limit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vx/workspace.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;64KiB&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// false keeps every entry as files&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If disk matters more than speed, &lt;code&gt;decompress: false&lt;/code&gt; keeps each entry&lt;br&gt;
as one &lt;code&gt;tar.zst&lt;/code&gt;, as in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  A local hit checks nothing
&lt;/h2&gt;

&lt;p&gt;Only vx writes to its local store, so checking each file on a hit would&lt;br&gt;
guard against nothing and slow every save.&lt;/p&gt;

&lt;p&gt;Anything from the network is checked: a remote entry ends with a CRC-32,&lt;br&gt;
and a wrong sum is a miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  A download is decompressed once
&lt;/h2&gt;

&lt;p&gt;Up to 4 MiB is fetched whole, decompressed once and written into the&lt;br&gt;
store, then checked. Larger ones go through a temp file, still&lt;br&gt;
decompressed once.&lt;/p&gt;

&lt;p&gt;Storage layout and settings: &lt;a href="https://vznjs.github.io/vx/caching/" rel="noopener noreferrer"&gt;Caching&lt;/a&gt; and the&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/schema/" rel="noopener noreferrer"&gt;schema&lt;/a&gt;. More at&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/a-hit-is-a-file-copy/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>vx on real repos: wall times</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Wed, 14 Oct 2026 22:01:32 +0000</pubDate>
      <link>https://dev.to/vzn-vx/vx-on-real-repos-wall-times-4odj</link>
      <guid>https://dev.to/vzn-vx/vx-on-real-repos-wall-times-4odj</guid>
      <description>&lt;p&gt;Seven open-source monorepos that already use Turborepo or Nx, each moved&lt;br&gt;
to native vx config and timed under both on one machine. Slower rows&lt;br&gt;
included.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQ1tjb2xkOiBubyBjYWNoZSwgbm8gb3V0cHV0c10gLS0-IFJbcmVzdG9yZTogY2FjaGUga2VwdCwgb3V0cHV0cyB3aXBlZF0KICBSIC0tPiBFW2VkaXQ6IG9uZSBmaWxlIGNoYW5nZWRdCiAgRSAtLT4gS1tjbG9uZTogZnJlc2ggY2hlY2tvdXQsIHNoYXJlZCBjYWNoZV0K%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQ1tjb2xkOiBubyBjYWNoZSwgbm8gb3V0cHV0c10gLS0-IFJbcmVzdG9yZTogY2FjaGUga2VwdCwgb3V0cHV0cyB3aXBlZF0KICBSIC0tPiBFW2VkaXQ6IG9uZSBmaWxlIGNoYW5nZWRdCiAgRSAtLT4gS1tjbG9uZTogZnJlc2ggY2hlY2tvdXQsIHNoYXJlZCBjYWNoZV0K%3FbgColor%3Dwhite" alt="diagram" width="1145" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;small&gt;4 vCPU Xeon 2.10GHz, 15 GB, Linux · Bun 1.4.2, Node 22.22.0 · vx 522c23b (create-t3-turbo, react-email, trpc, typescript-eslint), 731cf92 (vercel/ai, medusa, TanStack query), 1ee058c (react-email clone) · configs from &lt;code&gt;bunx @vzn/vx-migrate --native --no-install&lt;/code&gt; · concurrency 10 for all · local cache only, no daemon · 3 reps alternating, min shown, every run exit 0&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Seconds, min of 3. Shorter bars are less time; each table is one click&lt;br&gt;
away.&lt;/p&gt;

&lt;h3&gt;
  
  
  create-t3-turbo vs Turborepo 2.5.8
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;build typecheck&lt;/code&gt;, 11 tasks, commit 8f945b7.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Turborepo 2.5.8&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cold&lt;/td&gt;
&lt;td&gt;13.63 s&lt;/td&gt;
&lt;td&gt;10.97 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restore&lt;/td&gt;
&lt;td&gt;0.28 s&lt;/td&gt;
&lt;td&gt;0.09 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;4.69 s&lt;/td&gt;
&lt;td&gt;4.89 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit, clean: false&lt;/td&gt;
&lt;td&gt;4.61 s&lt;/td&gt;
&lt;td&gt;3.83 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clone&lt;/td&gt;
&lt;td&gt;0.28 s&lt;/td&gt;
&lt;td&gt;0.11 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both edit rows: vx 5b2b0a1, same machine, min of 3. By default vx&lt;br&gt;
deletes a task's outputs before a miss, tsc's &lt;code&gt;tsbuildinfo&lt;/code&gt; too, so tsc&lt;br&gt;
rebuilds from scratch.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cache.outputs.clean: false&lt;/code&gt; on the three tsc tasks keeps it.&lt;/p&gt;

&lt;h3&gt;
  
  
  react-email vs Turborepo 2.9.14
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;build&lt;/code&gt;, 4 tasks, commit 1531a39.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Turborepo 2.9.14&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cold&lt;/td&gt;
&lt;td&gt;28.92 s&lt;/td&gt;
&lt;td&gt;25.11 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restore&lt;/td&gt;
&lt;td&gt;0.32 s&lt;/td&gt;
&lt;td&gt;0.22 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;28.23 s&lt;/td&gt;
&lt;td&gt;26.29 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clone&lt;/td&gt;
&lt;td&gt;0.34 s&lt;/td&gt;
&lt;td&gt;0.26 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  trpc vs Turborepo 2.10.12
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;build&lt;/code&gt;, 7 tasks, commit d756e59.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Turborepo 2.10.12&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cold&lt;/td&gt;
&lt;td&gt;9.29 s&lt;/td&gt;
&lt;td&gt;7.48 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restore&lt;/td&gt;
&lt;td&gt;0.17 s&lt;/td&gt;
&lt;td&gt;0.15 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;9.40 s&lt;/td&gt;
&lt;td&gt;7.04 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clone&lt;/td&gt;
&lt;td&gt;0.16 s&lt;/td&gt;
&lt;td&gt;0.13 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  vercel/ai vs Turborepo 2.9.14
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;build&lt;/code&gt;, 82 tasks, commit e8c8e32.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Turborepo 2.9.14&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cold&lt;/td&gt;
&lt;td&gt;144.96 s&lt;/td&gt;
&lt;td&gt;135.33 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restore&lt;/td&gt;
&lt;td&gt;0.92 s&lt;/td&gt;
&lt;td&gt;0.45 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;156.38 s&lt;/td&gt;
&lt;td&gt;145.61 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clone&lt;/td&gt;
&lt;td&gt;0.97 s&lt;/td&gt;
&lt;td&gt;0.65 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  medusa vs Turborepo 1.13.4
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;build build:plugin&lt;/code&gt;, 83 tasks, commit 5018ab67.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Turborepo 1.13.4&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cold&lt;/td&gt;
&lt;td&gt;316.57 s&lt;/td&gt;
&lt;td&gt;307.81 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restore&lt;/td&gt;
&lt;td&gt;3.56 s&lt;/td&gt;
&lt;td&gt;3.45 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;268.92 s&lt;/td&gt;
&lt;td&gt;259.96 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clone&lt;/td&gt;
&lt;td&gt;2.59 s&lt;/td&gt;
&lt;td&gt;3.54 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Clone row&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;vx's clone takes what its restore takes (3.54 s and 3.45 s, all 83 hits).&lt;br&gt;
Turborepo's clone takes less time than its own restore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  TanStack query vs Nx 23.2.1
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;build&lt;/code&gt;, 25 tasks, commit 817bd02.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Nx 23.2.1&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cold&lt;/td&gt;
&lt;td&gt;35.01 s&lt;/td&gt;
&lt;td&gt;29.37 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restore&lt;/td&gt;
&lt;td&gt;0.94 s&lt;/td&gt;
&lt;td&gt;0.32 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;34.10 s&lt;/td&gt;
&lt;td&gt;29.03 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clone&lt;/td&gt;
&lt;td&gt;0.96 s&lt;/td&gt;
&lt;td&gt;0.58 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Clone row&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nx's first clone rep missed its cache (36.42 s): Nx 23 indexes its local&lt;br&gt;
cache in the checkout's own &lt;code&gt;.nx/workspace-data&lt;/code&gt;. The chart shows the&lt;br&gt;
minimum.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  typescript-eslint vs Nx 23.2.0
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;build&lt;/code&gt;, 18 tasks, commit 57e7cccce.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arm&lt;/th&gt;
&lt;th&gt;Nx 23.2.0&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cold&lt;/td&gt;
&lt;td&gt;52.50 s&lt;/td&gt;
&lt;td&gt;41.77 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;restore&lt;/td&gt;
&lt;td&gt;4.45 s&lt;/td&gt;
&lt;td&gt;5.32 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;edit&lt;/td&gt;
&lt;td&gt;40.69 s&lt;/td&gt;
&lt;td&gt;38.97 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;clone&lt;/td&gt;
&lt;td&gt;4.15 s&lt;/td&gt;
&lt;td&gt;5.37 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Restore and clone rows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;vx hits 16 of 18, Nx 17. &lt;code&gt;ast-spec#build&lt;/code&gt; stays a miss: its own test&lt;br&gt;
may rewrite its committed snapshots, and vx never trusts a key on files&lt;br&gt;
another task can write.&lt;/p&gt;

&lt;p&gt;Nx's first clone rep missed its cache (56.82 s), as with TanStack query.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Run it on yours
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bunx @vzn/vx init
vx run build &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then time your runner's command and &lt;code&gt;vx run&lt;/code&gt; on the same machine, each&lt;br&gt;
on its own cache. More at &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/vx-on-real-repos/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Say which projects may depend on which</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:06:45 +0000</pubDate>
      <link>https://dev.to/vzn-vx/say-which-projects-may-depend-on-which-3eek</link>
      <guid>https://dev.to/vzn-vx/say-which-projects-may-depend-on-which-3eek</guid>
      <description>&lt;p&gt;Tag your projects by layer and say which layers may depend on which. &lt;code&gt;vx&lt;br&gt;
lock&lt;/code&gt; refuses a dependency that breaks a rule.&lt;/p&gt;

&lt;p&gt;Here UI may not depend on a utility, and &lt;code&gt;packages/c&lt;/code&gt; (UI) depends on&lt;br&gt;
&lt;code&gt;packages/a&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVBQWyJ0YWcgdHlwZTphcHAiXSAtLT58YWxsb3dlZHwgRkVBVFsidGFnIHR5cGU6ZmVhdHVyZSJdCiAgQVBQIC0tPnxhbGxvd2VkfCBVSVsidGFnIHR5cGU6dWkiXQogIFVJIC0uLXh8ZGVuaWVkfCBGRUFUCiAgVUkgLS4teHxkZW5pZWR8IFVUSUxbInRhZyB0eXBlOnV0aWwiXQo%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgQVBQWyJ0YWcgdHlwZTphcHAiXSAtLT58YWxsb3dlZHwgRkVBVFsidGFnIHR5cGU6ZmVhdHVyZSJdCiAgQVBQIC0tPnxhbGxvd2VkfCBVSVsidGFnIHR5cGU6dWkiXQogIFVJIC0uLXh8ZGVuaWVkfCBGRUFUCiAgVUkgLS4teHxkZW5pZWR8IFVUSUxbInRhZyB0eXBlOnV0aWwiXQo%3FbgColor%3Dwhite" alt="diagram" width="671" height="182"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vx/workspace.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:util&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vx lock
✗ vx lock  lock not written

  1 dependency breaks run.rules.dependencies
  packages/c depends on packages/a, which run.rules.dependencies[0] &lt;span class="o"&gt;(&lt;/span&gt;from &lt;span class="s1"&gt;'tag:type:ui'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; denies

  fix  drop the dependency, or change the project&lt;span class="s1"&gt;'s tags or the rule
$ echo $?
1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The edge here is a task edge (&lt;code&gt;c#build&lt;/code&gt; depends on &lt;code&gt;a#build&lt;/code&gt;), not a&lt;br&gt;
&lt;code&gt;package.json&lt;/code&gt; dependency. Both count.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Demo workspace · vx 69617eb · 2026-10-23&lt;/small&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Tags and rules
&lt;/h2&gt;

&lt;p&gt;A project names its tags in its own config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// packages/button/vx.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineProject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineProject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tsc -b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workspace says what each tag may depend on. A selector is&lt;br&gt;
&lt;code&gt;tag:&amp;lt;glob&amp;gt;&lt;/code&gt; or a project-name glob:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vx/workspace.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A dependency must match one of &lt;code&gt;allow&lt;/code&gt; (if given) and none of &lt;code&gt;deny&lt;/code&gt;.&lt;br&gt;
A deny beats any allow. A project no rule selects is free.&lt;/p&gt;
&lt;h2&gt;
  
  
  vx lock checks them, runs don't
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;vx lock&lt;/code&gt; checks each project's direct dependencies: &lt;code&gt;package.json&lt;/code&gt; and&lt;br&gt;
cross-project &lt;code&gt;dependsOn&lt;/code&gt;. It names every broken edge and writes&lt;br&gt;
nothing. Fix the edge and the lock is written:&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="nv"&gt;$ &lt;/span&gt;vx lock
✓ vx lock  6 project configs → .vx/projects.lock.json

&lt;span class="nv"&gt;$ &lt;/span&gt;vx lock &lt;span class="nt"&gt;--check&lt;/span&gt;
✓ vx lock &lt;span class="nt"&gt;--check&lt;/span&gt;  up to &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;6 projects&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vx lock --check&lt;/code&gt; in CI fails the same way, so a pull request with a&lt;br&gt;
forbidden dependency goes red.&lt;/p&gt;

&lt;p&gt;Runs read the lock, which already passed the rules, so they pay nothing&lt;br&gt;
(&lt;a href="https://vznjs.github.io/vx/blog/lock-and-frozen/" rel="noopener noreferrer"&gt;vx lock&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  From Nx
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;nx()&lt;/code&gt; from &lt;code&gt;@vzn/vx-migrate&lt;/code&gt; reads the &lt;code&gt;depConstraints&lt;/code&gt; of&lt;br&gt;
&lt;code&gt;@nx/enforce-module-boundaries&lt;/code&gt; from your ESLint config and fills&lt;br&gt;
&lt;code&gt;run.rules.dependencies&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It reads the file without running it, so ESLint never loads on a vx run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint.config.mjs (Nx)&lt;/span&gt;
&lt;span class="nx"&gt;depConstraints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sourceTag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type:app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onlyDependOnLibsWithTags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type:feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sourceTag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;notDependOnLibsWithTags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;type:feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:ui&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tag:type:feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One difference: Nx's &lt;a href="https://github.com/nrwl/nx/blob/master/packages/eslint-plugin/src/rules/enforce-module-boundaries.ts" rel="noopener noreferrer"&gt;lint&lt;br&gt;
rule&lt;/a&gt;&lt;br&gt;
checks source imports. vx checks declared dependencies.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;allSourceTags&lt;/code&gt;, &lt;code&gt;/regex/&lt;/code&gt; tags and external-import bans are left out.&lt;br&gt;
The &lt;a href="https://github.com/vznjs/vx/tree/main/packages/vx-migrate#module-boundaries" rel="noopener noreferrer"&gt;vx-migrate&lt;br&gt;
README&lt;/a&gt;&lt;br&gt;
lists them.&lt;/p&gt;

&lt;p&gt;The schema is in the &lt;a href="https://vznjs.github.io/vx/schema/" rel="noopener noreferrer"&gt;configuration reference&lt;/a&gt;. More at&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/dependency-rules/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>One name per setting</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:00:43 +0000</pubDate>
      <link>https://dev.to/vzn-vx/one-name-per-setting-143g</link>
      <guid>https://dev.to/vzn-vx/one-name-per-setting-143g</guid>
      <description>&lt;p&gt;Every vx setting has one name. Its environment variable and flag are&lt;br&gt;
spelled from it, so learn one and you know all three.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run.retries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VX_RUN_RETRIES&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--retries&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run.concurrency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VX_RUN_CONCURRENCY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--concurrency&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cache.dir&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VX_CACHE_DIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--cache-dir&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cache.policy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;VX_CACHE_POLICY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--cache-policy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Settings live in four groups in &lt;code&gt;.vx/workspace.ts&lt;/code&gt;: &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;cache&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;lock&lt;/code&gt; and &lt;code&gt;watch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineWorkspace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vzn/vx/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineWorkspace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;build/.vx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which value wins
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgVFt0YXNrIGZpZWxkXSAtLT4gRlstLWZsYWddIC0tPiBWW1ZYXyB2YXJpYWJsZV0gLS0-IFdbLnZ4L3dvcmtzcGFjZS50c10gLS0-IERbZGVmYXVsdF0K%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FZmxvd2NoYXJ0IExSCiAgVFt0YXNrIGZpZWxkXSAtLT4gRlstLWZsYWddIC0tPiBWW1ZYXyB2YXJpYWJsZV0gLS0-IFdbLnZ4L3dvcmtzcGFjZS50c10gLS0-IERbZGVmYXVsdF0K%3FbgColor%3Dwhite" alt="diagram" width="873" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Left wins. &lt;code&gt;exec.retries&lt;/code&gt; on a task beats &lt;code&gt;--retries&lt;/code&gt;. &lt;code&gt;vx info&lt;/code&gt; shows&lt;br&gt;
each changed setting and where its value came from:&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="nv"&gt;$ VX_RUN_CONCURRENCY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 vx info
…
  settings          run.concurrency 2 &lt;span class="o"&gt;(&lt;/span&gt;VX_RUN_CONCURRENCY&lt;span class="o"&gt;)&lt;/span&gt;, run.retries 2 &lt;span class="o"&gt;(&lt;/span&gt;.vx/workspace&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Units are part of the value
&lt;/h2&gt;

&lt;p&gt;Durations carry a unit (&lt;code&gt;'500ms'&lt;/code&gt;, &lt;code&gt;'30s'&lt;/code&gt;, &lt;code&gt;'2h'&lt;/code&gt;), sizes too&lt;br&gt;
(&lt;code&gt;'32KiB'&lt;/code&gt;). A bare number is refused. Nobody can tell if &lt;code&gt;2000&lt;/code&gt; means&lt;br&gt;
milliseconds or seconds:&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="nv"&gt;$ VX_CONFIG_WORKER_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2000 vx run build
✗ vx run  VX_CONFIG_WORKER_TIMEOUT takes a duration with its unit, like &lt;span class="s1"&gt;'2000ms'&lt;/span&gt; or &lt;span class="s1"&gt;'2s'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;got 2000&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Unknown names are refused
&lt;/h2&gt;

&lt;p&gt;A typo in a key or a flag stops the run with the line to fix:&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="nv"&gt;$ &lt;/span&gt;vx run build
✗ vx run  config refused

  .vx/workspace.ts:4:3
  unknown field &lt;span class="s2"&gt;"concurrency"&lt;/span&gt;
  allowed: baseBranch, cache, lock, plugins, run, watch

    2 |
    3 | &lt;span class="nb"&gt;export &lt;/span&gt;default defineWorkspace&lt;span class="o"&gt;({&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 4 |   concurrency: 4,
      |   ^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few settings, like &lt;code&gt;run.outputLogs&lt;/code&gt; and &lt;code&gt;debug.timing&lt;/code&gt;, are read&lt;br&gt;
before the file loads, so only a variable or flag sets them.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Demo workspace · vx 22f6eb7 · 2026-10-23&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Every setting: &lt;a href="https://vznjs.github.io/vx/schema/workspace/#settings" rel="noopener noreferrer"&gt;settings table&lt;/a&gt;. More at&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/one-path-per-setting/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Where a task run spends its time</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Wed, 14 Oct 2026 16:54:42 +0000</pubDate>
      <link>https://dev.to/vzn-vx/where-a-task-run-spends-its-time-bie</link>
      <guid>https://dev.to/vzn-vx/where-a-task-run-spends-its-time-bie</guid>
      <description>&lt;p&gt;We traced one 8,002-task run through vx, Turborepo and Nx. A cold run is&lt;br&gt;
almost all task time. A warm run is how little the tool does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;th&gt;Turborepo 2.11.8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cold&lt;/td&gt;
&lt;td&gt;120.2 s&lt;/td&gt;
&lt;td&gt;127.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warm&lt;/td&gt;
&lt;td&gt;892 ms&lt;/td&gt;
&lt;td&gt;2,496 ms first, 909 ms second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One source file changed&lt;/td&gt;
&lt;td&gt;100.1 s (6,607 run)&lt;/td&gt;
&lt;td&gt;105.8 s (6,607 run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;That edit reverted&lt;/td&gt;
&lt;td&gt;3.6 s (1,321 tasks restored, the rest left in place)&lt;/td&gt;
&lt;td&gt;5.2 s (all 8,002 replayed)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;small&gt;Synthetic bench, 1,601 projects, 9,603 graph nodes · 4 cores, concurrency 10, &lt;code&gt;CI=1&lt;/code&gt;, local cache only, Linux · vx &lt;code&gt;9388bad8f&lt;/code&gt; (&lt;code&gt;VX_DEBUG_TIMING=1&lt;/code&gt;), Turborepo 2.11.8 (&lt;code&gt;--profile&lt;/code&gt;), Nx 23.3.0 (&lt;code&gt;nx:run-commands&lt;/code&gt;, daemon off, &lt;code&gt;.env&lt;/code&gt; off) · 2026-10-13/14 · &lt;a href="https://vznjs.github.io/vx/benchmarks/" rel="noopener noreferrer"&gt;method&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A cold run is the tasks
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FeHljaGFydC1iZXRhIGhvcml6b250YWwKICB0aXRsZSAiT25lIGJ1aWxkIHRhc2sgKG1zKSIKICB4LWF4aXMgWyJiYXJlIHNoZWxsIiwgInVuZGVyIHZ4Il0KICB5LWF4aXMgIm1zIiAwIC0tPiAzMjAKICBiYXIgWzMxMiwgMzE0XQo%3FbgColor%3Dwhite" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2FeHljaGFydC1iZXRhIGhvcml6b250YWwKICB0aXRsZSAiT25lIGJ1aWxkIHRhc2sgKG1zKSIKICB4LWF4aXMgWyJiYXJlIHNoZWxsIiwgInVuZGVyIHZ4Il0KICB5LWF4aXMgIm1zIiAwIC0tPiAzMjAKICBiYXIgWzMxMiwgMzE0XQo%3FbgColor%3Dwhite" alt="diagram" width="700" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;vx adds about 0.4 ms per task for spawn, env and output capture. Before&lt;br&gt;
the first task it spends about 640 ms on projects, graph and git.&lt;br&gt;
Turborepo's precompute is about 0.7 s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Some extra work is a safety check
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Graph rules (output overlap, missing edge)&lt;/td&gt;
&lt;td&gt;none at run time, &lt;code&gt;vx lock&lt;/code&gt; checks once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://vznjs.github.io/vx/blog/an-input-moves-four-tools/" rel="noopener noreferrer"&gt;Input recheck&lt;/a&gt; after a miss&lt;/td&gt;
&lt;td&gt;about 0.2 ms per miss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clear outputs before a miss&lt;/td&gt;
&lt;td&gt;about 0.15 ms per miss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save before dependents start&lt;/td&gt;
&lt;td&gt;at most about 0.14 s of 120 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blob-size check, cold cache&lt;/td&gt;
&lt;td&gt;about 330 ms, once per git index&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Remote uploads run in the background. Only the end of the run waits for&lt;br&gt;
them. We measured each check and kept it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A warm run decides not to work
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;th&gt;Turborepo 2.11.8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Projects and graph&lt;/td&gt;
&lt;td&gt;discover, then graph with rules&lt;/td&gt;
&lt;td&gt;precompute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File hashes&lt;/td&gt;
&lt;td&gt;git's own index (63 ms)&lt;/td&gt;
&lt;td&gt;git index, then per-package file hashes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keys&lt;/td&gt;
&lt;td&gt;one per task&lt;/td&gt;
&lt;td&gt;one per task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On a hit&lt;/td&gt;
&lt;td&gt;checks each task's outputs on disk; restores a task's outputs only if one differs&lt;/td&gt;
&lt;td&gt;restores every output and log&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without its daemon, Turborepo rewrites every output on a hit&lt;br&gt;
(&lt;a href="https://github.com/vercel/turborepo/blob/v2.11.8/crates/turborepo-run-cache/src/lib.rs" rel="noopener noreferrer"&gt;&lt;code&gt;restore_outputs&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;vx leaves a task's outputs alone if each file still matches its record&lt;br&gt;
(size, mode, modification time, inode, change time).&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx 23.3.0, warm: 21 to 24 s
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Self time&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;6.10 s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;hashTasksUpfront&lt;/code&gt;: hands each task's environment to the native hasher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.55 s&lt;/td&gt;
&lt;td&gt;garbage collection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.03 s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getTaskSpecificEnv&lt;/code&gt;: copies &lt;code&gt;process.env&lt;/code&gt; for each task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.89 s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;copyFilesFromCache&lt;/code&gt;: writes every cached output back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.56 s&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;plan&lt;/code&gt;, the native planner&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;small&gt;CPU profile of the main process, all 9,603 tasks hit, 173 env vars · source &lt;code&gt;nx@23.3.0&lt;/code&gt; &lt;code&gt;task-env.js&lt;/code&gt;, &lt;code&gt;cache.js&lt;/code&gt;&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getTaskSpecificEnv&lt;/code&gt; copies &lt;code&gt;process.env&lt;/code&gt; for every task, so it grows&lt;br&gt;
with your environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a change reruns
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;th&gt;Turborepo 2.11.8&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unrelated env var set&lt;/td&gt;
&lt;td&gt;all hit&lt;/td&gt;
&lt;td&gt;all hit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root &lt;code&gt;package.json&lt;/code&gt; edited&lt;/td&gt;
&lt;td&gt;all hit&lt;/td&gt;
&lt;td&gt;all 8,002 miss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One project's &lt;code&gt;package.json&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;6,607 miss&lt;/td&gt;
&lt;td&gt;6,607 miss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;bun.lock&lt;/code&gt; added&lt;/td&gt;
&lt;td&gt;all miss&lt;/td&gt;
&lt;td&gt;all miss&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;vx sees root dependencies through the lockfile. A lockfile plugin such as&lt;br&gt;
&lt;code&gt;bun()&lt;/code&gt; narrows the last row to the projects whose dependencies moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we checked in vx and dropped
&lt;/h2&gt;

&lt;p&gt;The output check on hits looked like 655 ms. Alone it is 10.8 ms over&lt;br&gt;
1,600 hits: the trace summed ten workers.&lt;/p&gt;

&lt;p&gt;A tool that rewrites outputs with the same bytes makes vx restore them,&lt;br&gt;
but a content hash on every save would cost more. Neither became a&lt;br&gt;
patch.&lt;/p&gt;

&lt;p&gt;The bench and its scripts are in the repository. More at&lt;br&gt;
&lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/where-a-run-spends-its-time/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>An input changes mid-run: what four task tools do</title>
      <dc:creator>VX</dc:creator>
      <pubDate>Tue, 13 Oct 2026 20:29:58 +0000</pubDate>
      <link>https://dev.to/vzn-vx/an-input-changes-mid-run-what-four-task-tools-do-3pj5</link>
      <guid>https://dev.to/vzn-vx/an-input-changes-mid-run-what-four-task-tools-do-3pj5</guid>
      <description>&lt;p&gt;You save a file while a build runs. The output may come from bytes the&lt;br&gt;
cache key does not name. Here is what each tool does after the command&lt;br&gt;
exits, read from its source.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;After the command exits&lt;/th&gt;
&lt;th&gt;Turborepo 2.11.7&lt;/th&gt;
&lt;th&gt;Nx 23.3.0&lt;/th&gt;
&lt;th&gt;Vite Task 1.1.0&lt;/th&gt;
&lt;th&gt;vx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inputs looked at again&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no (batch: rehash)&lt;/td&gt;
&lt;td&gt;files the task opened&lt;/td&gt;
&lt;td&gt;every declared input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writes it notices&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;the task's own&lt;/td&gt;
&lt;td&gt;any process's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it does on a change&lt;/td&gt;
&lt;td&gt;saves&lt;/td&gt;
&lt;td&gt;saves&lt;/td&gt;
&lt;td&gt;skips the save&lt;/td&gt;
&lt;td&gt;skips the save, names file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can be relaxed per task&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inputStability&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Turborepo 2.11.7
&lt;/h2&gt;

&lt;p&gt;It takes the hash before the run and saves the outputs under it.&lt;/p&gt;

&lt;p&gt;Inputs are not checked again&lt;br&gt;
(&lt;a href="https://github.com/vercel/turborepo/blob/v2.11.7/crates/turborepo-run-cache/src/lib.rs" rel="noopener noreferrer"&gt;&lt;code&gt;save_outputs&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Nx 23.3.0
&lt;/h2&gt;

&lt;p&gt;It hashes in &lt;code&gt;processTask&lt;/code&gt; and saves with &lt;code&gt;cache.put(task.hash, …)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In batch mode, a task that reads its dependencies' outputs is hashed&lt;br&gt;
again (&lt;a href="https://unpkg.com/nx@23.3.0/dist/src/tasks-runner/task-orchestrator.js" rel="noopener noreferrer"&gt;&lt;code&gt;task-orchestrator.js&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://unpkg.com/nx@23.3.0/dist/src/tasks-runner/cache.js" rel="noopener noreferrer"&gt;&lt;code&gt;cache.js&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Vite Task (vite-plus 1.1.0)
&lt;/h2&gt;

&lt;p&gt;It traces the files a task opens. If the task wrote a file it also read,&lt;br&gt;
it skips the save (&lt;code&gt;InputModified&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Writes by other processes are not traced&lt;br&gt;
(&lt;a href="https://github.com/voidzero-dev/vite-task/blob/7d69d6577ecf6bd83deee32186de59918a712873/crates/vt/src/session/execute/cache_update.rs" rel="noopener noreferrer"&gt;&lt;code&gt;cache_update.rs&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  vx
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vx run packages/api#build
┌─ packages/api#build &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export const r = &lt;/span&gt;&lt;span class="nv"&gt;$RANDOM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; src/routes.gen.ts &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; out &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp &lt;/span&gt;src/index.ts out/
&lt;span class="o"&gt;[&lt;/span&gt;vx] packages/api#build: &lt;span class="sb"&gt;`&lt;/span&gt;packages/api/src/routes.gen.ts&lt;span class="sb"&gt;`&lt;/span&gt; changed after its key was taken — the result stands, but is not saved under a key that no longer describes it&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;the task writes it, &lt;span class="nb"&gt;declare &lt;/span&gt;it &lt;span class="k"&gt;in &lt;/span&gt;cache.outputs
└─ packages/api#build ── &lt;span class="o"&gt;(&lt;/span&gt;6ms&lt;span class="o"&gt;)&lt;/span&gt; success
  cache     1 not saved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;small&gt;One-project workspace · vx &lt;code&gt;c1ad075&lt;/code&gt; · 2026-10-23&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;vx checks every declared input after the run, whoever wrote it. A&lt;br&gt;
changed input keeps the result and saves nothing, and tasks downstream&lt;br&gt;
skip their save too.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cache.inputStability&lt;/code&gt; relaxes it per task&lt;br&gt;
(&lt;a href="https://vznjs.github.io/vx/blog/inputs-that-move/" rel="noopener noreferrer"&gt;how&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Each one is a trade
&lt;/h2&gt;

&lt;p&gt;vx pays one &lt;code&gt;lstat&lt;/code&gt; per input on a run that saves. Turborepo and Nx skip&lt;br&gt;
that work. Vite Task checks what its tracer already saw. Which fits&lt;br&gt;
depends on how often your inputs move during a run.&lt;/p&gt;

&lt;p&gt;More at &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://vznjs.github.io/vx/blog/an-input-moves-four-tools/" rel="noopener noreferrer"&gt;vx blog&lt;/a&gt;. vx is open-source (MIT) task orchestration with a build cache: &lt;a href="https://vznjs.github.io/vx/" rel="noopener noreferrer"&gt;vznjs.github.io/vx&lt;/a&gt; · &lt;a href="https://github.com/vznjs/vx" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>monorepo</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
