Workspace config reference
Workspace config is a pair of JSON files that describe how HumanLayer creates task workspaces for a selected repository.
.humanlayer/workspace.jsonis shared repo or team configuration..humanlayer/workspace.local.jsonis sparse user or machine-specific override configuration.
This reference is intentionally exhaustive. For a setup walkthrough, see the workspace setup guide.
Tip: copy this page as Markdown and paste it into your coding agent when you want it to create, review, or debug .humanlayer/workspace.json and .humanlayer/workspace.local.json.
Files
Both files live under .humanlayer/ in the repository selected when creating a task:
selected-repo/.humanlayer/workspace.jsonshared repo/team configurationworkspace.local.jsonsparse user/machine overridesworkspace.json
workspace.json is the shared configuration file. It should contain values that make sense for the repo or team.
Commit this file when the config is useful for collaborators:
.humanlayer/workspace.jsonworkspace.local.json
workspace.local.json is an optional local override file. It should contain only values that differ on your machine.
Do not commit this file. Add it to .gitignore:
.humanlayer/workspace.local.jsonUse this file for local paths, local source refs, local setup commands, private file-copy rules, local-only repos, or local repo deletions.
File format
Both files are strict JSON:
- comments are not allowed
- trailing commas are not allowed
- unknown keys are not allowed
- keys are case-sensitive
reposmust be an array when presentcopyGlobsmust be an array of strings when present
All fields are optional. Missing fields are filled by defaults or inherited from broader config.
Effective configuration
HumanLayer does not use either file directly. It computes an effective configuration:
Built-in values for paths, branches, source refs, copy globs, and the default single repo.
Shared team config overrides defaults and adds shared repo settings.
Local overrides replace scalar fields, append copy globs, and merge repos by localPath.
Task creation uses the resolved values for every repo worktree.
The effective configuration is what task creation uses to render worktree paths, branch names, repos, copy globs, setup commands, and primary repo behavior.
The two important merge ideas are:
- most scalar fields override earlier values
copyGlobsappends and deduplicates instead of replacingreposmerges bylocalPathinstead of replacing the whole list
Root fields
Root fields appear at the top level of workspace.json or workspace.local.json.
disabled
Type: boolean
Default: false
When true, workspace setup options that require worktree creation are disabled.
{
"disabled": true
}Merge behavior: override. If workspace.local.json sets disabled, it wins over workspace.json. If only workspace.json sets it, that value wins over the default.
Use cases:
- disable automatic worktree setup for a repo where worktrees are not safe
- locally re-enable workspace setup when shared config disables it
- locally disable workspace setup while debugging your environment
pathTemplate
Type: string
Default: ~/.humanlayer/workspaces//
Template for each created worktree path.
{
"pathTemplate": "~/.humanlayer/workspaces/{{ TASKSLUG }}/{{ REPOBASENAME }}"
}Supported template variables:
- the task slug, such aseng-123-fix-login- the basename of the repo'slocalPath
Merge behavior: override. A local pathTemplate replaces the shared pathTemplate.
Notes:
~is expanded by native workspace setup.- Include
for multi-repo workspaces so each repo gets a separate worktree path. - Include
to keep each task workspace isolated.
branchTemplate
Type: string
Default:
Template for the branch name created in each worktree.
{
"branchTemplate": "{{ TASKSLUG }}"
}Supported template variables:
- the task slug
Merge behavior: override. A local branchTemplate replaces the shared branchTemplate.
Notes:
branchTemplateis root-level only.- Repo entries cannot set a different
branchTemplate. - All repos in a workspace use the same rendered branch name.
sourceRef
Type: string
Default: HEAD
Git ref used as the starting point for created worktrees unless a repo entry overrides it.
{
"sourceRef": "origin/main"
}Merge behavior: override at the root level. A repo-level sourceRef then overrides the effective root sourceRef for that repo only.
Common values:
HEAD- start from the currently checked-out commit in the source repomain- start from the localmainbranchorigin/main- start from the remote tracking ref- a commit SHA - start from a specific commit
setupCommand
Type: string
Default: empty string, meaning no command
Shell command to run after the worktree is created and after copyGlobs are copied.
{
"setupCommand": "bun install"
}Merge behavior: override at the root level. A repo-level setupCommand then overrides the effective root setupCommand for that repo only.
Execution behavior:
- runs inside the created worktree
- runs through the shell as
sh -c <command> - runs after file copying
- is skipped when the effective value is an empty string
Use setup commands for dependency installation or repo bootstrap steps that are safe in a new worktree.
copyGlobs
Type: string[]
Default:
[
".env",
".env.local",
".env.development.local",
".claude/settings.local.json",
".humanlayer/workspace.json",
".humanlayer/workspace.local.json"
]Glob patterns for files to copy from each source repo into its created worktree.
{
"copyGlobs": [".env", ".env.local", ".claude/settings.local.json"]
}Merge behavior: append and deduplicate, never replace.
The final root copyGlobs list is built by appending defaults, shared globs, and local globs, then removing duplicates.
Then each repo can append its own copyGlobs, and those repo-level patterns are also deduplicated.
Important: because copyGlobs is additive, workspace.local.json cannot remove a default or shared copy glob. It can only add more patterns.
repos
Type: array of repo entries
Default:
[
{
"localPath": ".",
"description": "Selected repository",
"primary": true
}
]Repositories that participate in workspace creation.
{
"repos": [
{
"localPath": ".",
"description": "Main repo",
"primary": true
},
{
"localPath": "../api",
"description": "API service"
}
]
}Merge behavior: merge by localPath. workspace.local.json does not replace the whole repos array. Instead, each local repo entry is applied independently:
- matching
localPath- merge into the shared repo entry - new
localPath- add a local-only repo - matching
localPathwith$patch: "delete"- remove that repo locally
See repos merge behavior for exact rules.
Repository fields
Repository fields appear inside objects in the repos array.
localPath
Type: string
Required for each repo entry.
Path to the repo, relative to the selected repository.
{
"localPath": "../api"
}Special value:
.means the selected repo itself
Examples:
.- the selected repo../api- sibling API repo../web- sibling web repo../packages/shared- nested sibling repo path
localPath is also the identity key for repo merging. If workspace.local.json contains a repo with the same localPath as workspace.json, the local entry modifies that repo.
description
Type: string
Optional human-readable label for the repo.
{
"localPath": "../api",
"description": "API service"
}Merge behavior: override. A local repo description replaces the shared repo description for the same localPath.
sourceRef
Type: string
Optional repo-specific git ref.
{
"localPath": "../api",
"sourceRef": "origin/main"
}Inheritance behavior: repo.sourceRef wins first, then the effective root sourceRef, then the default HEAD.
Merge behavior: override. A local repo sourceRef replaces the shared repo sourceRef for the same localPath.
setupCommand
Type: string
Optional repo-specific setup command.
{
"localPath": "../web",
"setupCommand": "pnpm install"
}Inheritance behavior: repo.setupCommand wins first, then the effective root setupCommand, then the default empty string.
Merge behavior: override. A local repo setupCommand replaces the shared repo setupCommand for the same localPath.
copyGlobs
Type: string[]
Optional repo-specific glob patterns to copy.
{
"localPath": "../api",
"copyGlobs": [".env.test"]
}Inheritance and merge behavior: append and deduplicate.
For each repo, the effective list includes default root globs, shared root globs, local root globs, shared repo globs, and local repo globs. HumanLayer appends those lists in order and removes duplicates.
Repo copyGlobs never replace inherited copyGlobs; they only add patterns.
primary
Type: boolean
Optional marker for the default launch/config repo.
{
"localPath": ".",
"primary": true
}Behavior:
- single-repo effective configs always resolve that repo as primary
- multi-repo configs use explicit
primary: true - if no repo is marked primary in a multi-repo config, the task form can ask the user to choose
- the primary repo's worktree is the default working directory for the task session
Merge behavior: override. A local repo primary value replaces the shared repo primary value for the same localPath.
Local-only fields
$patch
Type: string literal "delete"
Allowed only inside workspace.local.json repo entries.
Removes a repo from the effective config on your machine without changing shared config.
{
"repos": [
{
"localPath": "../web",
"$patch": "delete"
}
]
}Rules:
$patchis valid only in.humanlayer/workspace.local.json- the only supported value is
"delete" - the entry must include
localPathso HumanLayer knows which repo to remove - deleting a repo only affects your effective local config
Defaults
If neither file provides a value, HumanLayer uses these defaults:
{
"disabled": false,
"pathTemplate": "~/.humanlayer/workspaces/{{ TASKSLUG }}/{{ REPOBASENAME }}",
"branchTemplate": "{{ TASKSLUG }}",
"sourceRef": "HEAD",
"setupCommand": "",
"copyGlobs": [
".env",
".env.local",
".env.development.local",
".claude/settings.local.json",
".humanlayer/workspace.json",
".humanlayer/workspace.local.json"
],
"repos": [
{
"localPath": ".",
"description": "Selected repository",
"primary": true
}
]
}Defaults are part of effective config. They matter even when you omit fields from workspace.json.
Template variables
Two template variables are supported.
TASKSLUG
Available in:
pathTemplatebranchTemplate
Example:
{
"branchTemplate": "{{ TASKSLUG }}"
}If the task slug is eng-123-fix-login, the branch name becomes:
eng-123-fix-loginREPOBASENAME
Available in:
pathTemplate
Example:
{
"pathTemplate": "~/.humanlayer/workspaces/{{ TASKSLUG }}/{{ REPOBASENAME }}"
}For localPath: "../api", becomes api.
For localPath: ".", becomes the basename of the selected repo path.
Precedence, overrides, and merge rules
Root scalar fields use this precedence:
workspace.local.json
workspace.json
built-in defaults
This applies to:
disabledpathTemplatebranchTemplatesourceRefsetupCommand
For these fields, a local value replaces a shared value.
copyGlobs does not use replacement. It appends defaults, shared values, and local values, then deduplicates them.
repos does not use whole-array replacement. It merges shared and local repo entries by localPath.
disabled, pathTemplate, branchTemplate, sourceRef, and setupCommand use the most specific value.
Default, shared, local, and repo-specific glob lists are appended and deduplicated.
Repo entries match by localPath. Local entries can override, add, or delete repos.
workspace.local.json merge behavior
workspace.local.json is a sparse override. You only need to include the fields you want to change or append.
Given this shared config:
{
"pathTemplate": "~/.humanlayer/workspaces/{{ TASKSLUG }}/{{ REPOBASENAME }}",
"sourceRef": "HEAD",
"setupCommand": "bun install",
"copyGlobs": [".env"],
"repos": [
{
"localPath": ".",
"description": "Main repo",
"primary": true
},
{
"localPath": "../api",
"description": "API service"
},
{
"localPath": "../web",
"description": "Web frontend"
}
]
}And this local config:
{
"pathTemplate": "~/worktrees/{{ TASKSLUG }}/{{ REPOBASENAME }}",
"copyGlobs": [".env.local"],
"repos": [
{
"localPath": "../api",
"sourceRef": "origin/main",
"copyGlobs": [".env.api.local"]
},
{
"localPath": "../web",
"$patch": "delete"
},
{
"localPath": "../docs",
"description": "Docs site",
"setupCommand": "bun install"
}
]
}The effective behavior is:
pathTemplateis replaced by the local valuesourceRefremainsHEADat the root because local config did not set a rootsourceRef- root
setupCommandremainsbun install - root
copyGlobsbecomes defaults plus.envplus.env.local, deduplicated - repo
.remains from shared config - repo
../apiremains, with localsourceRefadded and.env.api.localappended to its copy globs - repo
../webis removed locally - repo
../docsis added locally
copyGlobs behavior
copyGlobs are copied from each source repo into that repo's created worktree.
Copy behavior:
- patterns are resolved relative to the source repo root
- matching files are copied into the same relative path in the worktree
- missing matches are ignored
- regular files are copied
- destination files can be overwritten
Merge behavior:
- default root globs are included first
- shared root globs are appended
- local root globs are appended
- shared repo globs are appended for that repo
- local repo globs are appended for that repo
- duplicates are removed while preserving first occurrence order
Example:
// workspace.json
{
"copyGlobs": [".env", ".env.shared"],
"repos": [
{
"localPath": "../api",
"copyGlobs": [".env.api"]
}
]
}// workspace.local.json
{
"copyGlobs": [".env.local"],
"repos": [
{
"localPath": "../api",
"copyGlobs": [".env.api.local"]
}
]
}The ../api repo receives default globs plus .env.shared, .env.local, .env.api, and .env.api.local, with duplicates removed.
repos merge behavior
Repo entries are identified by localPath.
Matching localPath
If a local repo entry has the same localPath as a shared repo entry, the entries merge.
Local scalar fields replace shared scalar fields:
descriptionsourceRefsetupCommandprimary
Local copyGlobs append to shared repo copyGlobs.
// workspace.json
{
"repos": [
{
"localPath": "../api",
"description": "API service",
"sourceRef": "HEAD",
"setupCommand": "bun install",
"copyGlobs": [".env.api"]
}
]
}// workspace.local.json
{
"repos": [
{
"localPath": "../api",
"sourceRef": "origin/main",
"copyGlobs": [".env.api.local"]
}
]
}Effective ../api behavior:
descriptionremainsAPI servicesourceRefbecomesorigin/mainsetupCommandremainsbun install- repo copy globs include both
.env.apiand.env.api.local
New localPath
If a local repo entry has a localPath not present in shared config, it is added to the effective repo list.
{
"repos": [
{
"localPath": "../docs",
"description": "Docs site"
}
]
}Delete patch
If a local repo entry has $patch: "delete", that repo is removed from the effective repo list.
{
"repos": [
{
"localPath": "../web",
"$patch": "delete"
}
]
}primary repo behavior
The primary repo is the default working directory for the task session.
Single-repo configs always resolve to one primary repo, even if primary is omitted.
Multi-repo configs should mark exactly one repo as primary:
{
"repos": [
{
"localPath": ".",
"primary": true
},
{
"localPath": "../api"
}
]
}If a multi-repo config does not mark a primary repo, the task form can ask the user to choose one for that task.
If more than one repo is marked primary, task creation may require correction before it can proceed. Keep shared config to exactly one primary repo for predictable behavior.
disabled behavior
disabled controls whether workspace setup choices that require worktree creation are available.
Shared disable:
{
"disabled": true
}Local re-enable:
{
"disabled": false
}Because disabled is a root scalar field, local config wins over shared config. This lets a team disable workspace setup by default while allowing a user to re-enable it locally, or lets a user disable setup locally without changing team config.
Validation rules
Workspace config is validated strictly.
Root object rules:
- must be valid JSON
- must be an object
- may contain only supported root fields
- all root fields are optional
disabledmust be boolean when presentpathTemplatemust be string when presentbranchTemplatemust be string when presentsourceRefmust be string when presentsetupCommandmust be string when presentcopyGlobsmust be an array of strings when presentreposmust be an array when present
Repo object rules:
- must contain
localPath localPathmust be a non-empty string- may contain only supported repo fields
descriptionmust be string when presentsourceRefmust be string when presentsetupCommandmust be string when presentcopyGlobsmust be an array of strings when presentprimarymust be boolean when present
Local repo object rules:
- supports all repo fields above
- may include
$patch $patch, when present, must be exactly"delete"$patchis not valid inworkspace.json
Missing, empty, or whitespace-only files are treated as missing. Invalid JSON and schema errors are reported as config errors.
Minimal examples
Minimal single-repo workspace.json
{
"repos": [
{
"localPath": "."
}
]
}This relies on defaults for path template, branch template, source ref, setup command, copy globs, and primary behavior.
Minimal local override
{
"pathTemplate": "~/worktrees/{{ TASKSLUG }}/{{ REPOBASENAME }}"
}This only changes the worktree location for the current machine.
Minimal multi-repo workspace.json
{
"repos": [
{
"localPath": ".",
"primary": true
},
{
"localPath": "../api"
},
{
"localPath": "../web"
}
]
}Complete examples
Complete single-repo workspace.json
{
"disabled": false,
"pathTemplate": "~/.humanlayer/workspaces/{{ TASKSLUG }}/{{ REPOBASENAME }}",
"branchTemplate": "{{ TASKSLUG }}",
"sourceRef": "HEAD",
"setupCommand": "bun install",
"copyGlobs": [
".env",
".env.local",
".env.development.local",
".claude/settings.local.json",
".humanlayer/workspace.local.json"
],
"repos": [
{
"localPath": ".",
"description": "Selected repository",
"primary": true
}
]
}Complete multi-repo workspace.json
{
"disabled": false,
"pathTemplate": "~/.humanlayer/workspaces/{{ TASKSLUG }}/{{ REPOBASENAME }}",
"branchTemplate": "{{ TASKSLUG }}",
"sourceRef": "HEAD",
"setupCommand": "bun install",
"copyGlobs": [
".env",
".env.local",
".humanlayer/workspace.local.json"
],
"repos": [
{
"localPath": ".",
"description": "Coordination repo",
"primary": true
},
{
"localPath": "../api",
"description": "API service",
"sourceRef": "origin/main",
"setupCommand": "bun install",
"copyGlobs": [".env.api"]
},
{
"localPath": "../web",
"description": "Web frontend",
"setupCommand": "pnpm install",
"copyGlobs": [".env.web"]
}
]
}Complete workspace.local.json override
{
"disabled": false,
"pathTemplate": "~/worktrees/{{ TASKSLUG }}/{{ REPOBASENAME }}",
"sourceRef": "origin/main",
"copyGlobs": [
".env.local",
".env.development.local"
],
"repos": [
{
"localPath": "../api",
"setupCommand": "bun install --frozen-lockfile",
"copyGlobs": [".env.api.local"]
},
{
"localPath": "../web",
"$patch": "delete"
},
{
"localPath": "../docs",
"description": "Local docs checkout",
"sourceRef": "origin/main",
"setupCommand": "bun install"
}
]
}This local override:
- re-enables workspace setup locally if shared config disabled it
- changes the local worktree base path
- changes the root source ref to
origin/main - appends local copy globs
- changes the API setup command locally
- appends an API-specific local copy glob
- removes the web repo locally
- adds a local-only docs repo
Field summary
| Field | File | Location | Type | Merge behavior |
|---|---|---|---|---|
disabled | both | root | boolean | local overrides shared overrides default |
pathTemplate | both | root | string | local overrides shared overrides default |
branchTemplate | both | root | string | local overrides shared overrides default |
sourceRef | both | root and repo | string | repo overrides root; local overrides shared |
setupCommand | both | root and repo | string | repo overrides root; local overrides shared |
copyGlobs | both | root and repo | string array | append and deduplicate |
repos | both | root | repo array | merge by localPath |
localPath | both | repo | string | required repo identity key |
description | both | repo | string | local overrides shared |
primary | both | repo | boolean | local overrides shared |
$patch | local only | repo | "delete" | removes matching repo locally |