OpenSpec Spec Generator field notes
Spec Kit · field notes

Spec Kit: what it is, why init fails, and the fixes that hold

The short answer: Spec Kit is GitHub's open source toolkit for spec driven development. You install a command line tool called specify, it writes a scaffold of markdown files into your repository, and your coding agent then fills those files in one at a time, in a fixed order, starting with the project principles and ending with the code.

The honest answer: almost nobody loses time on the method. They lose it on step one. On releases before v1.0, specify init called the GitHub releases API to find the current template bundle, which made the very first command the only one that needed the network. On a rate limited or blocked connection it died with a 403 or a 401, and you got no files at all.

That is why nearly every troubleshooting thread you will find is about init rather than about specs. Read the failures below as version scoped, because they are.

What it puts on your disk

Two directories, and knowing which is which saves you a lot of confusion later.

.specify/                      project level, written once
├── memory/
│   └── constitution.md        the rules every later step must obey
├── templates/                 the blanks the commands fill in
├── scripts/
│   ├── bash/                  create-new-feature.sh, setup-plan.sh,
│   │                          check-prerequisites.sh
│   └── powershell/            the same three, as .ps1
└── presets/

specs/001-your-feature/        feature level, written per feature
├── spec.md                    what and why, no tech choices
├── plan.md                    how, grounded in the constitution
├── tasks.md                   ordered work, dependencies marked
├── data-model.md
├── contracts/
└── research.md

The pipeline runs in one direction. /speckit.constitution sets the principles. /speckit.specify writes the requirements. /speckit.plan picks the stack, and it refuses to guess when the constitution already named one. /speckit.tasks breaks the plan into units small enough to review. /speckit.implement executes them.

Two commands sit between them and are optional, which is exactly why people skip them. /speckit.clarify asks you up to five targeted questions about the parts of the spec that are ambiguous, and writes your answers back into the file. /speckit.analyze reads spec, plan and tasks together and reports contradictions, unmeasurable requirements and requirements no task covers. It changes nothing. It only tells you.

Installing it takes one line, and the version tag in that line matters more than it looks:

uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@v0.9.5
specify --version
specify init my-project --integration claude

Pin the tag. On releases before v1.0, leaving it off meant init had to ask the API what the latest release was, and that question is the one that failed. Pinning also keeps two projects on the same scaffolding, which matters more once a team is involved.

Five ways init fails, with the text it prints

Ordered roughly by how often they turn up. Every error string below is reproduced as it appeared.

Failure 1

The releases API rate limits you

Windows PowerShell, init run inside an existing repository.

Error fetching release information GitHub API returned 403 for
https://api.github.com/repos/github/spec-kit/releases/latest

Anonymous calls to that endpoint are capped per hour, and shared office or CI addresses burn through the cap quickly. Nothing is wrong with your install. Pass a personal access token, written out, on the command line:

specify init . --ai claude --github-token=ghp_your_real_token

The failure mode people hit next is subtle. Writing --github-token GITHUB_TOKEN passes the eight characters GITHUB_TOKEN. The variable is not expanded, the token is never read, and the error does not change. Use the --github-token= form with the actual value.

Failure 2

A stale token turns the 403 into a 401

Same machine, one step later, once a token exists in the environment.

401 Unauthorized: GitHub API returned status 401 for
https://api.github.com/repos/github/spec-kit/releases/latest

This reads like the previous fix failed and it is not the same problem. If GITHUB_TOKEN or GH_TOKEN is set to something expired, the tool still sends it, and GitHub rejects a bad token where it would have served an anonymous request. The counterintuitive move is to remove the token you just added and try again with nothing:

# PowerShell
$env:GITHUB_TOKEN = $null
$env:GH_TOKEN = $null
specify init . --ai claude

# bash
unset GITHUB_TOKEN GH_TOKEN
specify init . --ai claude

If anonymous works, leave it alone. If it does not, put a freshly generated token back, inline. Do not put it in .env and expect it to be read.

Failure 3

uv is too old, and the traceback names the wrong thing

macOS, install appears to succeed.

No module named 'specify'

The package resolved and the entry point did not. Older uv releases handle this install badly, and the error points at Python when the actual culprit is the installer. One command:

uv self update
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git

Then confirm with specify --version. It should print something shaped like specify-cli 0.9.5. If your shell says the command does not exist, the install is fine and your PATH is a session behind. Open a new terminal.

Failure 4

The wrong Python answers, and the console cannot print the message

Windows, two interpreters installed.

You install Python 3.14.2, run python --version, and get 3.10.11. The old paths sit ahead of the new ones in Path, and priority is positional. Move the 3.14 entries to the top, then refresh the current session instead of rebooting:

$env:PATH = [Environment]::ExpandEnvironmentVariables(
    [Environment]::GetEnvironmentVariable("PATH", "User") + ";" +
    [Environment]::GetEnvironmentVariable("PATH", "Machine"))

That applies to the window you are in. Other open windows still hold the old value, which is worth remembering before you conclude the fix did not work.

On the same platform, once the tool actually runs, the console itself can be the next problem:

UnicodeEncodeError: 'gbk' codec can't encode character '\u2022'

The code page is GBK and the message contains a bullet. Two ways out, either is enough:

chcp 65001
$env:PYTHONIOENCODING = "utf-8"
Failure 5

There is no route to GitHub, and there will not be one

Air gapped, or a network where the API is blocked outright.

A proxy gets you part of the way:

$env:HTTP_PROXY  = "http://127.0.0.1:7890"
$env:HTTPS_PROXY = "http://127.0.0.1:7890"
specify init . --ai claude

If the proxy terminates TLS and the handshake fails, --skip-tls gets you through. If there is no route at all, stop trying to reach the API and build the template bundle yourself. Clone the repository, let the release script build the directory, then package it. The script calls zip, which Windows does not ship, so it dies at the last step and leaves the directory behind:

bash .github/workflows/scripts/create-release-packages.sh v0.3.2

python -c "import zipfile; from pathlib import Path; \
base = Path('.genreleases/sdd-claude-package-sh'); \
z = zipfile.ZipFile('.genreleases/spec-kit-template-claude-ps-v0.3.2.zip','w',zipfile.ZIP_DEFLATED); \
[z.write(f, f.relative_to(base)) for f in base.rglob('*') if f.is_file()]; z.close()"

Then point init at the file you just made rather than at the network:

specify init . --ai claude \
  --local-template .genreleases/spec-kit-template-claude-ps-v0.3.2.zip \
  --here --force

One more that catches people before any of this: if you name an agent that is not installed, init stops at the selection step. specify init . --ai claude --ignore-agent-tools skips the check and writes the files anyway. On current releases that flag is spelled --integration claude, and --ai is the older name for it.

What changed in v1.0

Worth knowing before you copy any of the above, including the parts of this page. From v1.0 onwards the templates ship inside the wheel itself, so specify init no longer resolves a release over the API and works without network access. The GitHub download path was retired rather than fixed.

Two consequences follow. The 401 and 403 failures above belong to v0.x, and if you are on v1.0 or later and still seeing them, your CLI is out of date rather than your network being blocked. And the local template workaround is no longer the recommended route, because there is nothing left to download.

One wrinkle to be aware of if you do go looking for that workaround: some write-ups pass a --local-template flag. The released CLI does not expose it. The supported spelling is --template-path, which takes a zip or a directory, and the matching environment variable is SPEC_KIT_TEMPLATE_PATH.

The matrix, as a file you can keep

All ten failures, including the ones that did not fit above. Symptom, the exact text, the environment, the cause, the fix, and the date it was first written up.

Download the matrix (CSV)

The file lives at https://openspec-mvp.pages.dev/spec-kit-init-failures.csv, served as text/csv, ten rows plus a header. It opens in Excel, Numbers or any text editor.

SymptomEnvironmentCauseFixWritten up
Init stops, nothing writtenWindows PowerShellAPI rate limit on releases--github-token=ghp_...2025-12-10
401 instead of 403token present in environmentexpired token still sentclear both vars, go anonymous2026-03-20
Token from .env ignoredany shellvariable name passed literally--github-token= with the value2026-03-20
No module named 'specify'macOS, older uventry point not resolveduv self update, reinstall2026-07-14
Reports the older Pythontwo interpreters on Pathold entries rank highermove new paths to the top2025-12-13
UnicodeEncodeError on a bulletGBK consolecharacter not encodablechcp 650012026-03-20
Fetch stalls, no errorbehind a proxyoutbound HTTPS blockedset proxy vars, --skip-tls2026-03-20
Same 401 or 403 foreverno route to GitHubinit resolves latest over APIlocal template zip2026-03-20
zip: command not foundWindows release scriptscript shells out to zipzip with Python zipfile2026-03-20
Stops at agent selectionagent not installedinit verifies the agent--ignore-agent-tools2025-12-10

The numbers, by date and by who reported them

Every figure is claimed by a specific write-up on a specific date. None of it is a benchmark and none of it transfers to your machine by default. Read it as other people's logs.

DateWho reported itWhat they reported
2025-12-10 A hands on walkthrough running init inside an existing repository Error fetching release information GitHub API returned 403 for https://api.github.com/repos/github/spec-kit/releases/latest. Resolved by generating a personal access token and passing it inline. No counts reported.
2025-12-13 A Windows install walkthrough with a pasted PowerShell session python --version printed 3.10.11 while 3.14.2 was installed. Installed uv 0.9.17 from a mirror, a 22.1 MB wheel at 49.9 MB/s. Fix was reordering the user Path and refreshing it in session.
2026-03-20 A troubleshooting write-up aimed at blocked networks 401 Unauthorized on the same releases URL, and UnicodeEncodeError: 'gbk' codec can't encode character '\u2022'. Templates built at v0.3.2, packaged with Python because zip is absent on Windows.
2026-06-08 Repository counters quoted in a workflow walkthrough 110,000 stars and 9,700 forks, with 105 community extensions and 22 presets.
2026-07-14 A macOS install walkthrough No module named 'specify' on an older uv, fixed with uv self update. specify --version printed specify-cli 0.9.5. Floor is Python 3.11.

Who should not use this

Most write-ups stop at the install instructions. This is the part they leave out.

  • You cannot reach GitHub and cannot clone it either. Init needs the release bundle. If you have no route to the repo at all, the local template path is closed to you and there is no third option.
  • You are writing something you will delete this week. A throwaway script, a spike, a demo for one meeting. The constitution alone costs more than the code.
  • Your agent is older than slash commands. The whole workflow is driven from the agent's chat window. An agent that cannot load them leaves you with markdown files and no way to run them.
  • You want the agent to make the technical decisions. The constitution exists to take those choices away from it. If you have no stack opinion and want one suggested, this tool will argue with you.
  • You are on a locked down machine and cannot touch environment variables. Every fix above needs one. Without that, each run costs you the same five minutes.
  • You will not read what it generates. The workflow only pays off if someone checks plan.md before implement runs. Skip that review and you have moved the guess from the code into a document, which is worse, because now the guess is signed off.

A preflight worth running before init

Six lines, in order. If one of them fails, stop there, because the errors downstream are all downstream of it.

python --version                 # 3.11 or newer
uv --version                     # then uv self update
specify --version                # prints the tag you pinned
$env:GITHUB_TOKEN = $null        # clear a stale token first
$env:GH_TOKEN = $null
specify check                    # reports what it can see

After init succeeds, restart your editor. The command files are on disk and the editor has not loaded them, so the slash commands look missing until it reloads.

Generate your first spec scaffold

Upgrades are the part nobody plans for

Init is idempotent. Run it again and it refreshes the scripts and templates, applies a new preset, or switches the integration you are working with. It also keeps a hash of every file it wrote, which is how it tells your edits apart from its own. Files you changed are left alone unless you pass --force.

That makes upgrades safer than most scaffolding tools manage, and it makes them quiet, which is the problem. A release can rename a flag, as v1.0 did when --ai became --integration, or change what init puts on disk, and nothing will fail while it happens. Pin the tag in your install command. After any upgrade, look at what changed under .specify/ before you start the next feature.

Everything after that is dull in a good way. Write the principles once, describe the feature, let the tool ask you the questions you would have skipped, read the plan while there is no code yet, and let the agent work the list.