blog.naokihaba.com

OSS activity in October 2026

In October, I fixed bugs in Vite+ and vite-task. I also opened a PR for the Nuxt Storybook module. On October 24, I gave a talk at Vue Fes Japan 2026. The talk was titled “Contributing to Vite+: Inside Stories”. The slides are on Docswell. The speaker notes are on Zenn (Japanese) and DEV (English).

voidzero-dev/vite-plus

fix(config): skip git config writes that change nothing (#2958)

vp config wrote to git config four times on every run. It wrote even when the values did not change. Each write takes .git/config.lock. So if you run it at the same time in linked worktrees, the later run fails with could not lock config file (#2849). I changed it to read the current values first. If the values are the same, it does not write.

However, the first run after a clone must write values. So if first runs overlap, it still fails. This needs a wait-and-retry mechanism. That design needs its own decisions, so I moved it to the next PR.

Before #2958, worktrees A and B write to .git/config every time, even when the values are the same, so B fails with could not lock config file. After #2958, they read the values first and skip the write when the values match, so they do not take the lock. However, the first run after a clone still writes, so they still conflict.

fix(config): wait for a shared git config lock on first run (#3199)

This is a follow-up to #2958 (#2959). git config in Git 2.55 and earlier does not wait for the lock to be released. So vp config retries writes that fail because of the lock. It waits 50 ms at first. It doubles the wait each time, and it retries up to five times. That makes a total of up to 1.55 seconds. Before each retry, it reads the value. If another worktree has already written the same value, it stops and reports success without writing.

While A holds the lock and writes, B’s writes fail at 0, 50, 150, and 350 ms. At 750 ms, B reads the value. A’s value is already there, so B succeeds without writing. If the lock is still held at the last retry at 1550 ms, the run ends with a Git error.

fix(env): call Nushell built-ins with the % sigil in env.nu (#3123)

Someone reported that Nushell does not start when you define a command with the same name as a built-in command. One example is def update [] (#3092). Nushell resolves command names when it parses. Vite+’s env.nu loads after the user’s config. So update in env.nu resolved to the user’s command. The arguments did not match, and Nushell stopped with nu::parser::extra_positional.

In env.nu, I now call built-in commands with the % sigil. For example, %update. Nushell 0.112.1 added the % sigil. So vp env setup checks the version of nu on PATH. It adds the sigil only for version 0.112.1 or later.

The user’s config defines def update. Then Vite+’s env.nu loads. Before the change, update in env.nu resolved to the user’s update. The arguments did not match, so Nushell does not start, with nu::parser::extra_positional. After the change, %update calls the built-in update, so Nushell starts.

fix(env): use the named completer input in env.nu for Nushell 0.116 (#3267)

Nushell 0.116 deprecates the form where a completer receives its input by position. Completers now receive it by name: buffer, place, and token (Unified custom completions). The completer in env.nu still received context by position. So a warning, Positional completer input deprecated, appeared on every completion (#3127). Completion stops working once the old form is removed. So I changed it now to use named input.

fix(env): fall back to file completion in Nushell without fish (#3276)

The completers for vp and vpr in env.nu call ^fish to build completion candidates. If fish is not on PATH, this call fails. In Nushell 0.116, no candidates appeared (#3272). In 0.115, file completion appeared in this case.

According to the Nushell Book, when a completer returns null, Nushell falls back to file completion. So I added one line to the completion function that vp and vpr share. When fish is missing, the line returns null.

fix(cli): set package-manager lifecycle env for vp run (#2895)

This PR takes over #2385, which had stalled (#2897). I talked with fengmk2 and agreed to take it over. I merged the changes from #2385 and resolved the conflicts. In #2398, the PackageManager field name changed from install_dir to bin_prefix. #2385 failed the CI build. So I also updated its references and tests to match the current API.

voidzero-dev/vite-task

fix(reporter): wait and retry when task output forwarding hits EAGAIN (#801)

On CI, when vp run output goes to a pipe, running two tasks at the same time could stop all tasks (#782, vite-plus#2824). I made a reproduction repository and tested it with Vite+ v1.0.0. It stopped in this sequence:

  1. The Node.js task sets the shared output to O_NONBLOCK. So the fd 1 of vp also becomes non-blocking.
  2. The other task prints a large amount of log output. vp writes it to the pipe.
  3. The pipe buffer fills up quickly.
  4. The write in vp returns EAGAIN without waiting.
  5. vp treats this as a fatal error. It stops all tasks.

If write or flush returns WouldBlock, it now waits and retries. Output to a terminal, a file, or a blocking pipe does not cause WouldBlock. So the behavior for those outputs does not change.

The output of task A enters vp through pipe ①. It then flows from vp’s fd 1 through pipe ② to the CI log. Node task B shares the same output as vp, and sets it to O_NONBLOCK. When pipe ② is full, vp’s write returns EAGAIN without waiting. Before the change, all tasks stopped. After #801, on WouldBlock, it waits and retries.

nuxt-modules/storybook

fix(addon): use Nuxt’s app context for Nuxt 4.6 compatibility (#1139)

In Nuxt 4.6.0, the place where the browser-side Nuxt instance is stored changed. It moved from unctx to globalThis.__nuxt_app_ctx__ (nuxt/nuxt#36371). This addon registered the instance directly in unctx. So in Nuxt 4.6, useNuxtApp() could not find the instance. It threw NUXT_E1001 (#1138). I changed the addon to use getNuxtAppCtx from #app/nuxt. I also removed the unctx dependency, which was no longer needed.

Up to Nuxt 4.5, the addon registers in unctx, and useNuxtApp() also reads unctx. In Nuxt 4.6, useNuxtApp() reads globalThis.nuxt_app_ctx. But the addon still registers in unctx, so it throws NUXT_E1001. After #1139, the addon registers with getNuxtAppCtx() in the place Nuxt uses.