Skip to content

Workspace config reference

Workspace config is a pair of JSON files that describe how HumanLayer creates task workspaces for a selected repository.

  • .humanlayer/workspace.json is shared repo or team configuration.
  • .humanlayer/workspace.local.json is 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 configuration
workspace.local.jsonsparse user/machine overrides

workspace.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:

text
.humanlayer/workspace.json

workspace.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:

text
.humanlayer/workspace.local.json

Use 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
  • repos must be an array when present
  • copyGlobs must 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:

1. Defaults

Built-in values for paths, branches, source refs, copy globs, and the default single repo.

2. workspace.json

Shared team config overrides defaults and adds shared repo settings.

3. workspace.local.json

Local overrides replace scalar fields, append copy globs, and merge repos by localPath.

4. Effective config

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
  • copyGlobs appends and deduplicates instead of replacing
  • repos merges by localPath instead 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.

json
{
  "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.

json
{
  "pathTemplate": "~/.humanlayer/workspaces/{{ TASKSLUG }}/{{ REPOBASENAME }}"
}

Supported template variables:

  • - the task slug, such as eng-123-fix-login
  • - the basename of the repo's localPath

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.

json
{
  "branchTemplate": "{{ TASKSLUG }}"
}

Supported template variables:

  • - the task slug

Merge behavior: override. A local branchTemplate replaces the shared branchTemplate.

Notes:

  • branchTemplate is 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.

json
{
  "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 repo
  • main - start from the local main branch
  • origin/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.

json
{
  "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:

json
[
  ".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.

json
{
  "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:

json
[
  {
    "localPath": ".",
    "description": "Selected repository",
    "primary": true
  }
]

Repositories that participate in workspace creation.

json
{
  "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 localPath with $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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

json
{
  "repos": [
    {
      "localPath": "../web",
      "$patch": "delete"
    }
  ]
}

Rules:

  • $patch is valid only in .humanlayer/workspace.local.json
  • the only supported value is "delete"
  • the entry must include localPath so 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:

json
{
  "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:

  • pathTemplate
  • branchTemplate

Example:

json
{
  "branchTemplate": "{{ TASKSLUG }}"
}

If the task slug is eng-123-fix-login, the branch name becomes:

text
eng-123-fix-login

REPOBASENAME

Available in:

  • pathTemplate

Example:

json
{
  "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:

Highest precedence

workspace.local.json

Shared fallback

workspace.json

Default fallback

built-in defaults

This applies to:

  • disabled
  • pathTemplate
  • branchTemplate
  • sourceRef
  • setupCommand

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.

Scalars override

disabled, pathTemplate, branchTemplate, sourceRef, and setupCommand use the most specific value.

copyGlobs append

Default, shared, local, and repo-specific glob lists are appended and deduplicated.

repos merge

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:

json
{
  "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:

json
{
  "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:

  • pathTemplate is replaced by the local value
  • sourceRef remains HEAD at the root because local config did not set a root sourceRef
  • root setupCommand remains bun install
  • root copyGlobs becomes defaults plus .env plus .env.local, deduplicated
  • repo . remains from shared config
  • repo ../api remains, with local sourceRef added and .env.api.local appended to its copy globs
  • repo ../web is removed locally
  • repo ../docs is 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:

jsonc
// workspace.json
{
  "copyGlobs": [".env", ".env.shared"],
  "repos": [
    {
      "localPath": "../api",
      "copyGlobs": [".env.api"]
    }
  ]
}
jsonc
// 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:

  • description
  • sourceRef
  • setupCommand
  • primary

Local copyGlobs append to shared repo copyGlobs.

jsonc
// workspace.json
{
  "repos": [
    {
      "localPath": "../api",
      "description": "API service",
      "sourceRef": "HEAD",
      "setupCommand": "bun install",
      "copyGlobs": [".env.api"]
    }
  ]
}
jsonc
// workspace.local.json
{
  "repos": [
    {
      "localPath": "../api",
      "sourceRef": "origin/main",
      "copyGlobs": [".env.api.local"]
    }
  ]
}

Effective ../api behavior:

  • description remains API service
  • sourceRef becomes origin/main
  • setupCommand remains bun install
  • repo copy globs include both .env.api and .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.

json
{
  "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.

json
{
  "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:

json
{
  "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:

json
{
  "disabled": true
}

Local re-enable:

json
{
  "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
  • disabled must be boolean when present
  • pathTemplate must be string when present
  • branchTemplate must be string when present
  • sourceRef must be string when present
  • setupCommand must be string when present
  • copyGlobs must be an array of strings when present
  • repos must be an array when present

Repo object rules:

  • must contain localPath
  • localPath must be a non-empty string
  • may contain only supported repo fields
  • description must be string when present
  • sourceRef must be string when present
  • setupCommand must be string when present
  • copyGlobs must be an array of strings when present
  • primary must be boolean when present

Local repo object rules:

  • supports all repo fields above
  • may include $patch
  • $patch, when present, must be exactly "delete"
  • $patch is not valid in workspace.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

json
{
  "repos": [
    {
      "localPath": "."
    }
  ]
}

This relies on defaults for path template, branch template, source ref, setup command, copy globs, and primary behavior.

Minimal local override

json
{
  "pathTemplate": "~/worktrees/{{ TASKSLUG }}/{{ REPOBASENAME }}"
}

This only changes the worktree location for the current machine.

Minimal multi-repo workspace.json

json
{
  "repos": [
    {
      "localPath": ".",
      "primary": true
    },
    {
      "localPath": "../api"
    },
    {
      "localPath": "../web"
    }
  ]
}

Complete examples

Complete single-repo workspace.json

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

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

json
{
  "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

FieldFileLocationTypeMerge behavior
disabledbothrootbooleanlocal overrides shared overrides default
pathTemplatebothrootstringlocal overrides shared overrides default
branchTemplatebothrootstringlocal overrides shared overrides default
sourceRefbothroot and repostringrepo overrides root; local overrides shared
setupCommandbothroot and repostringrepo overrides root; local overrides shared
copyGlobsbothroot and repostring arrayappend and deduplicate
reposbothrootrepo arraymerge by localPath
localPathbothrepostringrequired repo identity key
descriptionbothrepostringlocal overrides shared
primarybothrepobooleanlocal overrides shared
$patchlocal onlyrepo"delete"removes matching repo locally