thinslice.new-state
The thinslice.new-state executable is a script checked into your repo that generates configuration for your slice. It can be written in any language. thinslice runs it and expects TOML on stdout.
Discovery
When thinslice needs the template, it searches in this order:
- A matching
[["thinslice.new-state"]]entry in~/.config/thinslice/config.tomlwithmode = "override". - In the worktree, relative to its root:
./thinslice.new-state./src/thinslice.new-state./config/thinslice.new-state
- A matching
[["thinslice.new-state"]]entry in~/.config/thinslice/config.tomlwithmode = "fallback". - Otherwise, error.
The first one found is used. Make sure it's executable (chmod +x thinslice.new-state).
Most projects only need a thinslice.new-state checked into the repo. Steps 1 and 3 cover the case where you can't (or don't want to) commit the template to the repo; see External Template for the global-config entries.
What it does
When thinslice needs to generate configuration — during slice state new, slice up, or worktree creation with --from — it executes your template script and reads the TOML it produces.
Your script is responsible for:
- Declaring all environment variables that configure the slice
- Requesting port allocations from thinslice
- Defining what processes to run and how
- Defining any hooks for customizing workflows
- Setting up initial state directory contents (creating
pgdata/, etc.)
Because it's a real script rather than a DSL, you can do anything: conditional logic, environment detection, user prompts, platform-specific behavior.
Port allocation
Use slice give-me-a-port inside your template script to get a free port. Each call returns a different port that is not currently in use on your machine and doesn't collide with any slice in any project.
PG_PORT=$(slice give-me-a-port)
REDIS_PORT=$(slice give-me-a-port)
Port allocation is machine-wide: thinslice tracks ports across all projects and all slices on your laptop, so you can have many slices running simultaneously — even from different repos — without collisions. See Port Registry for the implementation details.
The [env] section
The TOML your template outputs must include an [env] section — a flat key=value mapping of every environment variable needed to configure the slice. Ports, connection strings, data directory paths — everything goes here. This makes thinslice.toml the single source of truth for both runtime configuration and process management.
[env]
PG_PORT = "5440"
REDIS_PORT = "6390"
API_PORT = "3010"
FRONTEND_HTTP_PORT = "8090"
DATABASE_URL = "postgresql://localhost:5440/myapp"
PGDATA = "/Users/you/src/foo/+state+/dev/pgdata"
REDIS_DIR = "/Users/you/src/foo/+state+/dev/redis"
When slice up starts each process, it sets all [env] variables in the process environment. Processes read their configuration from these environment variables — they need no knowledge of the thinslice directory structure. A process that wants to find its data directory reads $PGDATA and stops there.
After writing thinslice.toml, thinslice derives a flat env file from the [env] section (KEY=VALUE, one per line) for interop with shells and tools that source env files directly. thinslice.toml is the source of truth; env is a derived artifact regenerated whenever thinslice.toml changes. Tools that only need environment variables can use env without knowing anything about the thinslice structure:
source "$SLICE_STATE_DIR/env"
psql -p $PG_PORT
cargo run --bin api
Input variables
Your template script receives the following environment variables:
| Variable | Description |
|---|---|
SLICE_STATE_DIR | Absolute path to the state directory |
SLICE_CODE_DIR | Absolute path to the worktree |
SLICE_PROJECT_DIR | Absolute path to the project root |
BRANCH | Branch name of the worktree |
Example template
#!/usr/bin/env bash
set -euo pipefail
PG_PORT=$(slice give-me-a-port)
REDIS_PORT=$(slice give-me-a-port)
NATS_PORT=$(slice give-me-a-port)
API_PORT=$(slice give-me-a-port)
FRONTEND_HTTP_PORT=$(slice give-me-a-port)
# Initialize postgres data directory if it doesn't exist
if [ ! -d "$SLICE_STATE_DIR/pgdata" ]; then
initdb -D "$SLICE_STATE_DIR/pgdata" --no-locale -E UTF8
fi
# Initialize redis directory
mkdir -p "$SLICE_STATE_DIR/redis"
# Generate the API config file in the state directory
cat > "$SLICE_STATE_DIR/api-config.toml" <<CFG
[server]
port = $API_PORT
[database]
url = "postgresql://localhost:$PG_PORT/myapp"
[redis]
url = "redis://localhost:$REDIS_PORT"
[nats]
url = "nats://localhost:$NATS_PORT"
CFG
cat <<TOML
[env]
PG_PORT = "$PG_PORT"
REDIS_PORT = "$REDIS_PORT"
NATS_PORT = "$NATS_PORT"
API_PORT = "$API_PORT"
FRONTEND_HTTP_PORT = "$FRONTEND_HTTP_PORT"
DATABASE_URL = "postgresql://localhost:$PG_PORT/myapp"
PGDATA = "$SLICE_STATE_DIR/pgdata"
REDIS_DIR = "$SLICE_STATE_DIR/redis"
API_CONFIG_FILE = "$SLICE_STATE_DIR/api-config.toml"
[process.postgres]
command = "postgres -D $SLICE_STATE_DIR/pgdata -p $PG_PORT"
port = $PG_PORT
[process.redis]
command = "redis-server --port $REDIS_PORT --dir $SLICE_STATE_DIR/redis"
port = $REDIS_PORT
[process.nats]
command = "nats-server -p $NATS_PORT -sd $SLICE_STATE_DIR/nats"
port = $NATS_PORT
[process.api]
command = "cargo run --bin api -- --config \$API_CONFIG_FILE"
port = $API_PORT
[process.web]
command = "npm run dev -- --port \$FRONTEND_HTTP_PORT"
port = $FRONTEND_HTTP_PORT
# ── hooks ──────────────────────────────────────────
[hooks.up.dev]
tmux = """
new-window -n $BRANCH
split-v 70
send-top "\$SHELL"
send-bottom "slice logs --follow"
focus-top
"""
[hooks.up.full]
tmux = """
new-window -n $BRANCH
split-h
split-right-v
split-right-v
send-topleft "slice logs api"
send-topright "slice logs web"
send-bottomleft "psql -p $PG_PORT"
send-bottomright "\$SHELL"
focus-bottomright
"""
[hooks.new-worktree.claude]
tmux = """
new-window -n $BRANCH
split-h
send-left "cd $SLICE_CODE_DIR && claude \$@"
send-right "cd $SLICE_CODE_DIR && slice up"
focus-prev
"""
[hooks.new-worktree.vim]
tmux = """
new-window -n $BRANCH
split-h 70
send-left "cd $SLICE_CODE_DIR && vim \$@"
send-right "cd $SLICE_CODE_DIR && slice up dev"
focus-left
"""
TOML
Example in Python
#!/usr/bin/env python3
import subprocess
import os
def get_port():
result = subprocess.run(["slice", "give-me-a-port"], capture_output=True, text=True)
return result.stdout.strip()
state = os.environ["SLICE_STATE_DIR"]
pg_port = get_port()
redis_port = get_port()
api_port = get_port()
web_port = get_port()
# Initialize postgres if needed
pgdata = os.path.join(state, "pgdata")
if not os.path.isdir(pgdata):
subprocess.run(["initdb", "-D", pgdata, "--no-locale", "-E", "UTF8"])
# Generate API config file in the state directory
api_config = os.path.join(state, "api-config.toml")
with open(api_config, "w") as f:
f.write(f"""[server]
port = {api_port}
[database]
url = "postgresql://localhost:{pg_port}/myapp"
[redis]
url = "redis://localhost:{redis_port}"
""")
print(f"""
[env]
PG_PORT = "{pg_port}"
REDIS_PORT = "{redis_port}"
API_PORT = "{api_port}"
FRONTEND_HTTP_PORT = "{web_port}"
DATABASE_URL = "postgresql://localhost:{pg_port}/myapp"
PGDATA = "{state}/pgdata"
REDIS_DIR = "{state}/redis"
API_CONFIG_FILE = "{state}/api-config.toml"
[process.postgres]
command = "postgres -D {state}/pgdata -p {pg_port}"
port = {pg_port}
[process.redis]
command = "redis-server --port {redis_port} --dir {state}/redis"
port = {redis_port}
[process.api]
command = "cargo run --bin api -- --config $API_CONFIG_FILE"
port = {api_port}
[process.web]
command = "npm run dev -- --port $FRONTEND_HTTP_PORT"
port = {web_port}
""")
Tips
Since your template is just a script, you can:
- Read environment variables to change behavior per-developer (
$USER,$EDITOR) - Detect which tools are installed and adjust accordingly
- Prompt the user for input during initial setup
- Source shared configuration from other files in your repo
- Use
jq,yq, or any other tool to generate complex configs - Initialize database schemas, run migrations, seed data
Keep the template in version control. It's part of your project, not part of thinslice.

