---
title: "Port registry: global coordination across projects"
description: "How thinslice keeps track of which ports are already in use across every project on your machine, so it never hands out one that's already busy."
---
# Port registry: global coordination across projects

This is an implementation detail of `slice give-me-a-port`. It is not part of
the public API and is invisible to [template](template.md) scripts and programs.

## Problem

The base problem I'm trying to solve is what port can I use that is free right
now. Some kind of `slice give-me-a-port`.

Now many would say its bonus points for giving me a port will still be free
over the next week or so. To those that say bind() is all you need I say
"expect more from your tools." I never know when I'm going to get to show
someone one of the several ideas I'm juggling day to day, so I like to keep
multiple worktrees in demo ready state.

Now you should have an idea why I really want `slice give-me-a-port` to avoid
port conflicts not just within a single project but across every project on my
laptop.

Now technically all I need to do is write down a list of all the projects using
thinslice, and then every time I need a new port
1) Start with the ~65k ports for 127.0.0.1
2) I can scan all of those `/**/+state+/*/thinslice.toml` files on my laptop.
3) after subtracting out the "claimed" ports, find one port not open using bind()


My assumption is that a developer laptop has thousands of available ports and
only a handful in active use. I don't actually know how coordinate with the OS.
I'm not going to build in a list of "avoid" other programs. I think thinslice
will work with just a crude best-effort registry.


I'm  going to ignore the problem of the registry and
reality drifting apart by handwaving towards some sort of `slice doctor` that
can rebuild the global registry from scratch. When thinslice thinks you can use
a port, but then say a week later you discover some other program wants to use
that port too... my plan is to have you manually run slice give-me-a-port again
and hand edit the specific thinslice.toml file in conflict, and then rebuild the
global registry cache.

## Solution

`slice` maintains a global cache as the `port_assignments` table inside
a SQLite database at:

```
$XDG_DATA_HOME/thinslice/thinslice.db
```

Falling back to `~/.local/share/thinslice/thinslice.db` when `XDG_DATA_HOME` is unset,
per the XDG Base Directory Specification.

SQLite is not required, I just felt like it. Technically all we need is list of
claimed ports, that could just be a text file. However, I'm the kind of person
who likes collecting time series data and graphing things. Once I decided to
have some kind of state file, I might as well make it SQLite just in case I get
a free weekend and want to stash more debugging data, like command timings.

## Schema

```sql
CREATE TABLE port_assignments (
    port         INTEGER NOT NULL,
    project_root TEXT    NOT NULL,  -- absolute path to the +clone+ parent dir
    slot_name    TEXT    NOT NULL,  -- name of the slot inside +state+
    process_display_name TEXT NOT NULL,  -- key from [process.*] in thinslice.toml
    assigned_at  TEXT    NOT NULL,  -- ISO-8601 timestamp
    PRIMARY KEY (port)
);
```

A port appears at most once. The combination of `(project_root, slot_name,
process_display_name)` identifies what owns it.

## Read/write lifecycle

**On `slice give-me-a-port`:** reads all rows from `port_assignments` to build
the global exclusion set, then combines it with the session-local reservation
list (ports already issued in the current template run) and the `bind()` test.
Picks the first port not excluded by any of these three checks. Writes the
chosen port to the registry immediately with a provisional entry
(`process_display_name = "__pending__"`) so that subsequent calls within the same
template run see it as taken even if `slice state new` has not yet written
`thinslice.toml`.

**On `slice state new` completing successfully:** replaces any `__pending__` rows for
this slot with final entries sourced from the `[process.*]` sections of the
written `thinslice.toml`.

**On `slice state new` failing or being interrupted:** the session tempfile is
cleaned up by `slice state new`, and provisional `__pending__` rows for that run are
deleted. A crash before cleanup leaves orphan rows, which the reconciliation
pass handles.

## Reconciliation

Registry entries can become stale when slots are deleted outside of slice
commands (e.g., `rm -rf +state+/slot-a`). `slice give-me-a-port` runs a
best-effort reconciliation pass before reading the exclusion set, with a
**100 ms wall-clock budget**:

1. For each row in `port_assignments`, check whether
   `<project_root>/+state+/<slot_name>/thinslice.toml` exists and still declares
   that port for that process.
2. Delete any row where the file is missing or no longer claims the port.
3. Stop early if the budget is exhausted — leave remaining rows for next time
   or for `slice doctor`.

The pass is intentionally shallow. Its goal is to prevent the most common
sources of drift (deleted slots, renamed projects) without making
`slice give-me-a-port` slow. It is not a consistency guarantee.

For a full, unbounded reconciliation across all known projects, run
`slice doctor`. This rescans every `thinslice.toml` file referenced in the
registry, rebuilds the global view from scratch, and reports any discrepancies
it finds.

## slice debug

`slice debug` is a namespace of diagnostic commands for inspecting thinslice
internals. These are not part of normal workflow; they exist to help understand
what thinslice thinks is true when something seems wrong.

```
slice debug port <number>
```

Looks up a specific port in the registry and reports what thinslice believes owns
it, or confirms it is unregistered. Useful when a process is failing to bind
and you want to know if thinslice has already claimed that port for another slot.

```
slice doctor
```

Runs an unbounded reconciliation pass: scans every `thinslice.toml` referenced in
the registry, removes stale entries, and reports what it changed. Run this if
`slice debug registry` shows entries you do not recognize, or after manually
deleting slots or moving project directories.
