What is Go workspace mode, and what problem does it solve?
Quick Answer
Workspace mode, introduced in Go 1.18 alongside generics, lets you work on multiple local modules simultaneously without editing each module's go.mod to add temporary replace directives pointing at local paths. A go.work file at the root of your workspace lists the local module directories to include (use ./moduleA and use ./moduleB), and the go command then resolves imports between them using the local, on-disk versions instead of whatever's published, letting you make a change in one module and immediately see its effect in another that depends on it, with no need to commit, tag, or push anything first. go.work is meant to be a local, developer-machine-only file, not checked into version control, since it reflects one developer's specific local setup, not the project's actual dependency requirements.
Detailed Answer
Before workspace mode, working across multiple related modules locally meant either publishing intermediate versions constantly, or hand-editing go.mod replace directives and remembering to revert them before committing.
The old workaround: replace directives
// go.mod, before workspace mode
module github.com/you/app
require github.com/you/shared-lib v1.0.0
replace github.com/you/shared-lib => ../shared-lib // easy to forget to remove before committing
This worked, but it required editing go.mod itself, and it was a constant source of "oops, I accidentally committed a local replace directive" mistakes.
The workspace mode approach
myproject/
app/
go.mod # module github.com/you/app
shared-lib/
go.mod # module github.com/you/shared-lib
go.work # ties them together, lives outside both modules' go.mod files
// go.work
go 1.22
use ./app
use ./shared-lib
cd myproject
go build ./app/... # resolves github.com/you/shared-lib from ../shared-lib automatically
No replace directive needed in either module's go.mod. Editing shared-lib's code and rebuilding app immediately picks up the change, with neither go.mod file needing any temporary edits at all.
Why go.work shouldn't be committed
go work init ./app ./shared-lib # creates go.work locally
go.work reflects one specific developer's local directory layout and which modules they happen to be actively co-developing at that moment — it's not part of the project's actual dependency graph, which is why the convention is to keep it out of version control (often via .gitignore), similar to a personal IDE config file.
The problem this solves, concretely
Say you need to make a coordinated change across two or more modules you maintain together, like a shared library and an app consuming it. Workspace mode lets you iterate on both simultaneously, with real, immediate feedback. There's no friction from publishing intermediate versions or managing temporary replace directives by hand.