Skip to content

[Bug]: Spurious "Keybindings updated" toasts on Windows when keybindings.json is only read (last-access time change) #23734

Open

Description

@zmooter

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

On Windows with NTFS last-access updates on. This is the default "System Managed" mode (fsutil behavior query disablelastaccess → 2 (System Managed, Last Access Time Updates ENABLED)):

  1. Open T3 Code and leave it idle. Don't edit ~/.t3/userdata/keybindings.json.
  2. Have any process read keybindings.json when its last-access time is over about an hour old. Examples: type %USERPROFILE%\.t3\userdata\keybindings.json, an agent grepping ~/.t3, Defender, Windows Search, or backup/sync tools.
  3. A "Keybindings updated – Keybindings configuration reloaded successfully." toast appears.

The underlying watcher behavior can be reproduced in plain Node (no T3 involved):

const fs = require('fs'), path = require('path');
const dir = path.resolve('watchtest'); fs.mkdirSync(dir, { recursive: true });
const f = path.join(dir, 'keybindings.json'); fs.writeFileSync(f, '[]');
fs.utimesSync(f, new Date(Date.now() - 3 * 3600e3), fs.statSync(f).mtime); // make atime stale
setTimeout(() => {
  const w = fs.watch(dir, (ev, name) => console.log('EVENT', ev, name));
  setTimeout(() => { fs.readFileSync(f); /* read only */ setTimeout(() => w.close(), 1500); }, 500);
}, 1500);
// Output on Windows: "EVENT change keybindings.json"

Expected behavior

The toast (and the keybindingsUpdated push) should only happen when the keybindings config actually changes.

Actual behavior

"Keybindings updated" toasts show up at random times during normal use, while keybindings.json itself stays the same (on my machine its mtime is still the original write from 2 days ago; only its atime changes).

Cause, from reading the shipped bundle:

  • apps/server/src/keybindings.ts → startWatcher calls fs.watch(keybindingsConfigDir) and filters on event.path === "keybindings.json", debounced 100ms, then calls revalidateAndEmit. That function invalidates the cache, reloads from disk and always emitChanges, with no comparison against the previous state.
  • On Windows, libuv's ReadDirectoryChangesW mask includes FILE_NOTIFY_CHANGE_LAST_ACCESS. So when a plain read bumps the atime, Node reports change keybindings.json. NTFS only bumps the atime about once per hour, which is why the toasts come at irregular intervals instead of in a loop.
  • subscribeServerConfig forwards each change as a { type: "keybindingsUpdated" } event. In the web app, the config-event handler (p8/handle in main-*.js) shows the success toast for every such event that isn't within 2s of the previous one.

Suggested fix: in revalidateAndEmit, skip emitChange when the reloaded { keybindings, issues } equal the cached state (or when mtime/size didn't change). The client could also skip the toast when the resolved keybindings didn't change.

Impact

Minor bug or occasional failure

Version or commit

0.1.2-nightly.20261022.3526

Environment

Windows 11 (10.0.26200), T3 Code (Nightly) desktop app, NTFS with DisableLastAccess = 2 (System Managed, enabled)

Logs or stack traces

$ stat keybindings.json
atime=2026-10-22 10:00:25 -0400   mtime=2026-10-20 09:16:01 -0400

Workaround

fsutil behavior set disablelastaccess 1 (admin + reboot) turns off last-access updates for the whole system. Excluding ~/.t3/userdata from Windows Search and Defender cuts down on readers but doesn't remove all of them.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions