Workspaces
How the extension decides which folder things belong to — especially when several repositories are open at once.
- Two ways to open a project
- The three scopes
- Where the workspace folder is
- Which folder a run uses
- Specs across repositories
- Run history
- What a plain folder sees
Two ways to open a project
VS Code opens either a folder or a .code-workspace file — a
JSON file that lists several folders, usually one repository each. This
extension behaves differently in the two, and the difference is worth
knowing before you hit it.
Open a folder and there are two scopes: the folder’s own
.local-workflows/ is the project’s, and your profile at
~/.local-workflows/ sits behind it.
Open a .code-workspace and a third scope appears in the middle: the
workspace itself. It belongs to no single repository and it is not your
personal settings. It is where a team keeps what all their repositories
share.
The three scopes
| Scope | Where | Who it is for |
|---|---|---|
| Folder | <repo>/.local-workflows/, anywhere in the tree |
this repository |
| Workspace | beside the open .code-workspace |
every repository in it |
| Profile | ~/.local-workflows/ |
you, everywhere |
All three hold the same things — tasks.yml, settings.json,
workflows/, plugins/, styles/. Three entries are single-scope:
specs/ is workspace-only, and db/ (run history) and events/ (what
each run printed) are profile-only. The workspace scope is not special;
it just sits in the middle.
The narrowest scope wins. If a repository declares a task named
deploy, the repository’s deploy runs. Copies with the same name in
the workspace or your profile are skipped, with no error — that is what
the scopes are for. A workspace copy beats a profile copy the same way.
folder → beats → workspace → beats → profile
The sidebar lists the scopes in this same order, so what you read is what runs.
Where the workspace folder is
By default, next to the .code-workspace file:
D:\Work\team\team.code-workspace
D:\Work\team\.local-workflows\ <- the workspace scope
D:\Work\repos\api\.local-workflows\ <- a folder scope
D:\Work\repos\web\.local-workflows\ <- another
If you want it somewhere else — say the .code-workspace lives in a
config repository, or the shared files belong elsewhere — set the place
in the workspace file itself:
{
"folders": [
{ "path": "../repos/api" },
{ "path": "../repos/web" }
],
"settings": {
"localWorkflows.workspaceRoot": "../shared"
}
}
Relative paths start from the folder that holds the .code-workspace,
just like its own folders: entries. They do not start from where you
launched the editor — that would make the same file mean different
things on different machines.
This setting is optional - it is the only one this extension keeps in
the editor, and most workspaces need it only when specs and settings
belong somewhere other than beside the .code-workspace file.
Which folder a run uses
A multi-root workspace creates one problem.
A task in api/.local-workflows/tasks.yml runs in api. That is clear.
But a task from the workspace scope or your profile — git clean -fdx,
say — belongs to no repository. If five are open, which one did you
mean?
There is no safe default, so you are asked, every time you run a workspace- or profile-level task that has not said where it runs. The answer is used for that one run and is not remembered.
Not remembering is deliberate. A remembered answer means you stop seeing the question — and you notice that the morning you run a migration against last week’s checkout.
You are asked even when only one folder is possible. A dialog whose outcome is already decided teaches people to dismiss dialogs.
The choices are the folders the .code-workspace lists, minus the
workspace root — that one holds shared definitions and is often not a
repository at all.
Both ways of starting a run ask: the play icon on the row, and the Run button inside the panel. A panel left open since yesterday is not aimed at anything now, so it asks again instead of quietly reusing the last folder.
Saying where it runs, so you are not asked
The question is worth asking about git clean -fdx. It is pointless for
a workflow whose only task calls an HTTP endpoint — there is nothing to
aim, and the answer was never going to be used.
Write a cwd: at the top of the file, and the question stops:
| Written | Means |
|---|---|
cwd: none |
This acts on no folder. The run starts in the file’s own project folder. |
cwd: ./api |
Run here. Relative to the folder the run starts in. |
cwd: ${{ home }}/scratch |
Same, with templating. |
Any of them silences the pick, because all three answer the same
question. The same key works on a single task in a tasks.yml, and a
task’s own cwd: resolves inside its file’s.
none is matched whatever the casing. If you really do have a folder
called none, write ./none.
See Workflows that need no folder for a complete file.
Who is never asked
A repository’s own tasks already have their folder. A definition that
declares a cwd: has answered already. A plain-folder window has only
one possible answer and keeps using it.
Specs across repositories
A developer changing five services for one feature writes one spec. Copying it into all five repositories would be worse than writing none, and picking one of the five at random would be worse still.
So with a .code-workspace open, specs live under the workspace root
rather than in each repository: .local-workflows/specs inside whichever
folder localWorkflows.workspaceRoot names.
"settings": {
"localWorkflows.workspaceRoot": "D:\\Work\\team-specs"
}
A backslash in JSON must be doubled — "D:\Work" is an invalid escape,
and "D:\team" silently becomes a tab character. Forward slashes work
too and need no escaping.
That folder can be anywhere, including a repository the workspace does
not list. A separate specs repository is exactly the case this exists
for - and because the same pointer decides where settings.json is read
from, the team’s process settings travel with the specs.
Leave it out and both go beside the .code-workspace file - the same
layout a single folder uses, one level up. You are not asked, and nothing
is written into your workspace file.
Where specs live defaults to
.local-workflows/specsunder the workspace root;sdd.specsDirinsettings.jsonmoves it — workspace-relative or absolute. What the pointer moves is the root, and a relativespecsDirfollows it.
With a
.code-workspaceopen, custom styles are read from the workspace’s.local-workflows/styles/instead of from each open repository, andsettings.jsonis read from the workspace root plus your profile rather than from each repository. In a plain folder, both work exactly as they always have.
Run history
History is stored per workspace, under
~/.local-workflows/db/<workspace>/run.db.
With a .code-workspace open, that key is the workspace file
itself, not one of its folders. So reordering the folders: array —
an edit that changes nothing about the work — does not start a fresh
history.
What a run printed
Not in that database. It is one file per run, beside it:
~/.local-workflows/events/<workspace>/runs/2026-09-05T20-14-03-a1b2c3d4.jsonl
~/.local-workflows/events/<workspace>/specs/dark-mode.jsonl
One JSON record per line, appended as the run goes and read back whole when you reopen it. A workflow or task run is one execution, so it gets one file, named so the folder sorts into the order things happened. A spec is not an execution — it is drafted and revised for as long as it lives — so it is one growing file per spec, and running a phase again appends rather than erasing what the last attempt argued.
Files rather than rows because this is the one thing nobody queries: you can delete one run’s output by hand, and a file damaged by a crash costs that one run rather than the workspace’s whole history.
What a plain folder sees
Nothing on this page applies. No third scope, no folder question, no
workspace settings read, and specs go in this folder’s own
.local-workflows/specs. If you
never open a .code-workspace, none of this exists for you.
The same is true of a multi-root workspace assembled in the window but never saved — with no file on disk it is treated as a plain folder until you save it.