You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I searched existing issues and did not find a duplicate.
I am describing a concrete problem or use case, not just a vague idea.
Area
apps/server
Problem or use case
Actions are stored per project checkout. project.created always writes scripts: [] (apps/server/src/orchestration/decider.ts:254), and nothing seeds them, so every new project and every additional checkout starts empty. Actions I want everywhere, like "Open Branch in Fork" or "Open in Xcode", have to be recreated or re-imported by hand each time.
Checking them into a repo's t3.json only partly helps. The import is still one click per checkout, and until it happens runOnWorktreeCreate: true silently does nothing, because the setup runner reads the project record rather than the file (ProjectSetupScriptRunner.ts:126). Nothing tells you the worktree was never initialized.
Proposed solution
Support a user-level t3.json in the server's state directory, at ~/.t3/userdata/t3.json, alongside keybindings.json and settings.json. It would carry a scripts array using the existing T3ProjectFileScript schema.
When a project is created, seed its actions from that file instead of the current hardcoded []. Seeded entries become ordinary project actions, so editing, keybindings, the worktree setup runner, and the mobile action list all work with no further changes. Seeding needs to happen where every create path passes, since normalizeDispatchCommand runs only in the transports (ws.ts, http.ts) and would miss auto-bootstrap (serverRuntimeStartup.ts:207) and the CLI (cli/project.ts:479).
Why this matters
Global actions become configurable once instead of once per checkout. These are per-user tool preferences rather than repo configuration, so a checked-in t3.json is the wrong home for them regardless of the import friction.
It also makes the existing global keybinding story work: ids are slugified from the action name (normalizeScriptId, apps/web/src/projectScripts.ts:35), so a global action named "Dev" is script.dev.run in every project, and a single rule in keybindings.json fires everywhere instead of dying silently in projects that lack the action.
Unlike #5063, this needs no repository trust gate. The file is user-authored and lives outside any repo, the same trust level as keybindings.json today.
Smallest useful scope
Read scripts from ~/.t3/userdata/t3.json at project creation and use it in place of the hardcoded empty array. New projects only, no settings UI, no editing from the app, no changes to the projection read path.
Alternatives considered
Merging the user-level actions into projects at read time would keep one live source of truth, but project shells are constructed in about five places in ProjectionSnapshotQuery.ts, and merged entries would need a source marker so persistScripts (which replaces the whole array) doesn't silently copy them into a project on the first edit. Worth doing later; too much for a first pass.
Merging in the clients is not smaller, since web and mobile both render project actions and the server-side setup runner still would not see them.
A global scripts array in settings.json works but splits action config across two formats when t3.json already defines this shape.
#5063 solves the adjacent repo-supplied case and needs a trust gate this does not.
Risks or tradeoffs
Seeding copies rather than links, so later edits to the user-level file will not reach existing projects. This matches the repo-level import, which is also a copy. A follow-up could add an "import from your global t3.json" entry to the existing import menu for projects that already exist, reusing importFileScript.
~/.t3 is on the machine running the server, so global actions are per-server, matching keybindings.json. Remote environments would each need their own file.
If the file defines an action with runOnWorktreeCreate: true, it becomes that project's setup action, since only one is allowed (setupProjectScript takes the first flagged one). Seeding is the only writer at creation time, so there is no conflict on day one, but the rule should be stated.
An invalid user-level file should be ignored with a logged warning, matching keybindings.json, rather than blocking project creation.
Examples or references
Related: #5063 (repo-supplied setup actions without per-user import), #5687 (editing or deleting an action leaves stale keybindings). #5090 proposes a dedicated "Open in" setting with a global default; a user-level t3.json would cover that case generically alongside any other per-user tooling action.
This discussion was converted from issue #6523 on August 15, 2026 09:54.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Before submitting
Area
apps/server
Problem or use case
Actions are stored per project checkout.
project.createdalways writesscripts: [](apps/server/src/orchestration/decider.ts:254), and nothing seeds them, so every new project and every additional checkout starts empty. Actions I want everywhere, like "Open Branch in Fork" or "Open in Xcode", have to be recreated or re-imported by hand each time.Checking them into a repo's
t3.jsononly partly helps. The import is still one click per checkout, and until it happensrunOnWorktreeCreate: truesilently does nothing, because the setup runner reads the project record rather than the file (ProjectSetupScriptRunner.ts:126). Nothing tells you the worktree was never initialized.Proposed solution
Support a user-level
t3.jsonin the server's state directory, at~/.t3/userdata/t3.json, alongsidekeybindings.jsonandsettings.json. It would carry ascriptsarray using the existingT3ProjectFileScriptschema.When a project is created, seed its actions from that file instead of the current hardcoded
[]. Seeded entries become ordinary project actions, so editing, keybindings, the worktree setup runner, and the mobile action list all work with no further changes. Seeding needs to happen where every create path passes, sincenormalizeDispatchCommandruns only in the transports (ws.ts,http.ts) and would miss auto-bootstrap (serverRuntimeStartup.ts:207) and the CLI (cli/project.ts:479).Why this matters
Global actions become configurable once instead of once per checkout. These are per-user tool preferences rather than repo configuration, so a checked-in
t3.jsonis the wrong home for them regardless of the import friction.It also makes the existing global keybinding story work: ids are slugified from the action name (
normalizeScriptId,apps/web/src/projectScripts.ts:35), so a global action named "Dev" isscript.dev.runin every project, and a single rule inkeybindings.jsonfires everywhere instead of dying silently in projects that lack the action.Unlike #5063, this needs no repository trust gate. The file is user-authored and lives outside any repo, the same trust level as
keybindings.jsontoday.Smallest useful scope
Read
scriptsfrom~/.t3/userdata/t3.jsonat project creation and use it in place of the hardcoded empty array. New projects only, no settings UI, no editing from the app, no changes to the projection read path.Alternatives considered
Merging the user-level actions into projects at read time would keep one live source of truth, but project shells are constructed in about five places in
ProjectionSnapshotQuery.ts, and merged entries would need a source marker sopersistScripts(which replaces the whole array) doesn't silently copy them into a project on the first edit. Worth doing later; too much for a first pass.Merging in the clients is not smaller, since web and mobile both render project actions and the server-side setup runner still would not see them.
A global
scriptsarray insettings.jsonworks but splits action config across two formats whent3.jsonalready defines this shape.#5063 solves the adjacent repo-supplied case and needs a trust gate this does not.
Risks or tradeoffs
Seeding copies rather than links, so later edits to the user-level file will not reach existing projects. This matches the repo-level import, which is also a copy. A follow-up could add an "import from your global t3.json" entry to the existing import menu for projects that already exist, reusing
importFileScript.~/.t3is on the machine running the server, so global actions are per-server, matchingkeybindings.json. Remote environments would each need their own file.If the file defines an action with
runOnWorktreeCreate: true, it becomes that project's setup action, since only one is allowed (setupProjectScripttakes the first flagged one). Seeding is the only writer at creation time, so there is no conflict on day one, but the rule should be stated.An invalid user-level file should be ignored with a logged warning, matching
keybindings.json, rather than blocking project creation.Examples or references
Related: #5063 (repo-supplied setup actions without per-user import), #5687 (editing or deleting an action leaves stale keybindings). #5090 proposes a dedicated "Open in" setting with a global default; a user-level
t3.jsonwould cover that case generically alongside any other per-user tooling action.Contribution
All reactions