This tutorial shows you how to register repos as projects and run tasks on feature branches — all from Telegram, without touching a terminal. Jump between repos from wherever you are; your machine handles the checkout.
What you'll learn: How to target repos from anywhere with /<project-alias>, and run on branches with @branch.
So far, Untether runs in whatever directory you started it. If you want to work on a different repo, you have to:
- Stop Untether
cdto the other repo- Restart Untether
Projects fix this. Once you register a repo, you can target it from chat—even while Untether is running elsewhere.
Navigate to the repo and run untether init:
cd ~/dev/happy-gadgets
untether init happy-gadgetsOutput:
saved project 'happy-gadgets' to ~/.untether/untether.toml
This adds an entry to your config (Untether also fills in defaults like worktrees_dir, default_engine, and sometimes worktree_base):
=== "untether config"
```sh
untether config set projects.happy-gadgets.path "~/dev/happy-gadgets"
```
=== "toml"
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
```
!!! tip "Project aliases are also Telegram commands"
The alias becomes a /command you can use in chat. Keep them short and lowercase: myapp, backend, docs.
Now you can start Untether from another repo. If you don't specify a project, Untether runs in the directory where you launched it.
cd ~/dev/your-project
untetherAnd target the project by prefixing your message:
!!! user "You" /happy-gadgets explain the authentication flow
Untether runs the agent in ~/dev/happy-gadgets, not your current directory.
The response includes a context footer:
!!! untether "Untether"
dir: happy-gadgets
codex resume abc123
That dir: line tells you which project is active. When you reply, Untether automatically uses the same project—you don't need to repeat /happy-gadgets.
Worktrees let you run tasks on feature branches without touching your main checkout. Instead of git checkout, Untether creates a separate directory for each branch.
Add worktree config to your project:
=== "untether config"
```sh
untether config set projects.happy-gadgets.path "~/dev/happy-gadgets"
untether config set projects.happy-gadgets.worktrees_dir ".worktrees"
untether config set projects.happy-gadgets.worktree_base "main"
```
=== "toml"
```toml
[projects.happy-gadgets]
path = "~/dev/happy-gadgets"
worktrees_dir = ".worktrees" # where branches go
worktree_base = "main" # base for new branches
```
!!! note "Ignore the worktrees directory"
Add .worktrees/ to your global gitignore so it doesn't clutter git status:
sh echo ".worktrees/" >> ~/.config/git/ignore
Use @branch after the project:
!!! user "You" /happy-gadgets @feat/new-login add rate limiting to the login endpoint
Untether:
- Checks if
.worktrees/feat/new-loginexists (and is a worktree) - If the branch exists locally, it adds a worktree for it
- If the branch doesn't exist, it creates it from
worktree_base(or the repo default) and adds the worktree - Runs the agent in that worktree
The response shows both project and branch:
!!! untether "Untether"
dir: happy-gadgets @feat/new-login
codex resume xyz789
Replies stay on the same branch. Your main checkout is untouched.
Once you've set a context (via /<project-alias> @branch or by replying), it sticks:
!!! user "You" /happy-gadgets @feat/new-login add tests
!!! untether "Untether" dir: happy-gadgets @feat/new-login
!!! user "reply to the bot's answer" also add integration tests
!!! untether "Untether" dir: happy-gadgets @feat/new-login
The dir: line in each message carries the context forward.
If you mostly work in one repo, set it as the default:
=== "untether config"
```sh
untether config set default_project "happy-gadgets"
```
=== "toml"
```toml
default_project = "happy-gadgets"
```
Now messages without a /<project-alias> prefix go to that repo:
!!! user "You" add a health check endpoint
Goes to happy-gadgets automatically.
Here's a typical workflow:
untether!!! user "You" /happy-gadgets review the error handling
!!! user "You" /happy-gadgets @feat/caching implement caching
!!! untether "Untether" dir: happy-gadgets @feat/caching
!!! user "You"
also add cache invalidation
!!! user "You" /backend @fix/memory-leak profile memory usage
!!! user "You" /happy-gadgets bump the version number
All from the same Telegram chat, without restarting Untether or changing directories.
Full options for [projects.<alias>]:
| Key | Default | Description |
|---|---|---|
path |
(required) | Repo root. Expands ~. |
worktrees_dir |
.worktrees |
Where branch worktrees are created (relative to the project path). |
worktree_base |
null |
Base branch for new worktrees. If unset, Untether uses origin/HEAD, the current branch, or master/main (in that order). |
default_engine |
null |
Engine to use for this project (overrides global default). |
chat_id |
null |
Bind a Telegram chat/group to this project. |
"unknown project"
Run untether init <alias> in the repo first.
Branch worktree not created
Make sure the worktrees directory (default .worktrees) is writable. If you've customized worktrees_dir, verify that path exists or can be created.
Context not carrying forward
Make sure you're replying to a message with a dir: line. If you send a new message (not a reply), context resets unless you have a default_project.
Worktree conflicts with existing branch
If the branch already exists locally, Untether uses it. For a fresh start, delete the worktree and the branch, or pick a new branch name.
You've got projects and branches working. The final tutorial covers using multiple engines effectively.
