21 KiB
Commands
ctask new
Create a new task or project workspace and launch the agent.
ctask new [title] [flags]
If title is omitted, generates task-HHMMSS.
Flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--category |
-c |
general (task) / projects (project) |
Workspace category subdirectory |
--project |
off | Create a long-lived project workspace (uses CTASK_PROJECT_ROOT if set, runs git init, project CLAUDE.md) |
|
--shell |
off | Open interactive shell instead of agent | |
--agent |
-a |
claude |
Command to exec as the agent |
--no-launch |
off | Create workspace only, do not launch | |
--container |
off | Deferred to a future release |
Examples:
ctask new "fix auth bug"
ctask new -c scripts "backup helper"
ctask new --no-launch "json cleanup"
ctask new --shell "test env"
ctask new --agent aider "refactor api"
ctask new
ctask new --project "billing service"
ctask new --project -c backend "billing service"
When --no-launch is used, no session is started and no session log is written.
Project mode (--project)
--project is a thin variation on the normal task workflow for longer-lived work. It changes:
task.yamlrecordstype: projectandlaunch_dir: <slug>(v0.5+)- Default category becomes
projects - Workspace root falls back to
CTASK_PROJECT_ROOTif set; otherwiseCTASK_ROOT - Built-in CLAUDE.md is the project-oriented template (overridable via seed directories)
- Seed order: built-in defaults -> general seed (
CTASK_SEED_DIR) -> project seed (CTASK_SEED_PROJECT_DIR) git initruns ifgitis on PATH; a minimal.gitignore(.ctask/+logs/sessions.log) is created only if no.gitignorewas already provided by a seed- If
gitis not available, ctask prints[ctask] git not found; skipped repository initializationand continues - A project subdirectory named after the final suffixed slug is created inside the workspace. ctask does not seed any files inside it -- the user places their own CLAUDE.md, source code, and project structure there.
Workspace layout (v0.5):
2026-04-22_litlink-v2/
├── .ctask/ ctask state (lease, manifest, summary, lock)
├── .git/ single git repo at workspace root
├── .gitignore
├── CLAUDE.md ctask workspace rules (managed by ctask + seed)
├── notes.md
├── task.yaml includes launch_dir: "litlink-v2"
├── context/ reference material, imported specs
├── output/ ctask deliverables
├── logs/ session logs
└── litlink-v2/ project subdirectory (user's codebase)
When ctask resume litlink-v2 runs, the agent is launched inside litlink-v2/. Both the workspace CLAUDE.md (root) and any project CLAUDE.md the user places inside litlink-v2/ apply to the session -- Claude Code reads CLAUDE.md hierarchically.
Project root semantics:
CTASK_PROJECT_ROOTnot set: workspace goes under$CTASK_ROOT/projects/<date>_<slug>(default categoryprojectsis appended)CTASK_PROJECT_ROOTset, no-c: workspace goes directly under$CTASK_PROJECT_ROOT/<date>_<slug>(noprojects/subdirectory is appended)CTASK_PROJECT_ROOTset, explicit-c <category>: workspace goes under$CTASK_PROJECT_ROOT/<category>/<date>_<slug>
Single git repo rule:
Project workspaces use a single git repository initialized at the workspace root. Do not create nested git repositories inside the workspace -- including inside the project subdirectory. If your project code lives in a subdirectory, it is tracked by the root repo.
Changing the launch directory:
The launch_dir field in task.yaml controls which subdirectory the agent is launched into on ctask resume, ctask last, and ctask open. By default it is set to the project slug. To launch from the workspace root instead, edit task.yaml and set launch_dir: "". To launch from a deeper path (e.g., backend/api), set launch_dir: "backend/api". No CLI command is provided -- manual edit is the only supported way to change it.
If launch_dir points to a directory that does not exist (deleted, renamed) or is not a directory, ctask prints a warning and falls back to the workspace root. Absolute paths and paths that escape the workspace via .. are errors and abort the session.
Seed directories
On ctask new, after writing the built-in defaults, ctask copies the contents of an optional user seed directory into the workspace. Files in the seed directory overwrite the built-in defaults; subdirectories are preserved recursively. task.yaml and .ctask/ at the seed root are always skipped.
| Variable | Default (Unix) | Default (Windows) |
|---|---|---|
CTASK_SEED_DIR |
~/.config/ctask/seed/ |
%APPDATA%\ctask\seed\ |
CTASK_SEED_PROJECT_DIR |
~/.config/ctask/seed-project/ |
%APPDATA%\ctask\seed-project\ |
The general seed is applied to every workspace. The project seed is applied only when --project is set, on top of the general seed (project seed wins). Both directories are optional; missing directories are silently ignored.
ctask list
List workspaces in reverse-chronological order.
ctask list [flags]
By default, ctask list shows all active workspaces -- both tasks and projects. Use --task or --projects to narrow by type, and --all to include archived workspaces.
Flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--all |
-a |
off | Include archived workspaces |
--task |
off | Show task workspaces only | |
--projects |
off | Show project workspaces only | |
--category |
-c |
all | Filter by category |
--limit |
-n |
20 | Maximum entries to show |
--task and --projects are mutually exclusive; passing both returns a usage error.
Examples:
ctask list # active tasks AND projects
ctask list --all # everything (incl. archived)
ctask list --task # active tasks only
ctask list --task --all # all tasks (incl. archived)
ctask list --projects # active projects only
ctask list --projects --all # all projects (incl. archived)
ctask list -c scripts -n 5
Output columns: status, type, mode, category, date, slug.
Workspaces created before v0.3 (which have no type field in task.yaml) are treated as tasks.
ctask resume
Reopen an existing workspace and launch the agent.
ctask resume <query> [flags]
Resolves the workspace by query (exact directory name, exact slug, or case-insensitive substring). Archived workspaces are excluded by default.
Flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--shell |
off | Open shell instead of agent | |
--agent |
-a |
from task.yaml | Override agent command |
--force |
off | Skip active-session and stale-workspace warnings | |
--container |
off | Deferred to a future release |
Examples:
ctask resume auth-bug
ctask resume backup
ctask resume --shell auth-bug
ctask resume --agent aider auth-bug
ctask resume --force auth-bug
If multiple workspaces match, prints all matches and exits. If none match, prints an error.
Session logging runs automatically: file changes during the session are recorded in logs/sessions.log.
ctask open
Open a workspace directory in an interactive shell without launching the agent.
ctask open <query> [flags]
Spawns a new subshell in the workspace directory. Does not modify the caller's shell session.
Flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--all |
-a |
off | Include archived workspaces in query resolution |
--force |
off | Skip active-session and stale-workspace warnings |
Examples:
ctask open auth-bug
ctask info
Display metadata and path for a workspace without entering it.
ctask info <query> [flags]
Flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--all |
-a |
off | Include archived workspaces in query resolution |
Examples:
ctask info auth-bug
ctask info backup
Shows: slug, title, category, status, mode, agent, created/updated timestamps, path, and directory contents. For v0.5 project workspaces, also shows Launch dir, Launch path, and Dir exists.
ctask archive
Mark a workspace as archived. The workspace stays in place but is hidden from default listings and query resolution.
ctask archive <query>
Examples:
ctask archive auth-bug
To see archived workspaces, use ctask list --all. To resolve archived workspaces in other commands, use the --all flag where available.
ctask last
Resume the most recently updated workspace, considering both tasks and projects. Equivalent to ctask resume on whichever active workspace has the latest updated_at timestamp. Archived workspaces are excluded.
ctask last [flags]
Flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--shell |
off | Open shell instead of agent | |
--agent |
-a |
from task.yaml | Override agent command |
--force |
off | Skip active-session and stale-workspace warnings |
Examples:
ctask last
ctask last --shell
If no active workspaces exist, prints an error and exits.
ctask doctor
Verify that ctask is correctly set up. Read-only -- never modifies anything.
ctask doctor
Checks:
- Workspace root exists and is writable
- Default agent command is found on PATH
- Status-line helper script exists at the expected location
- Claude Code
statusLineis configured in~/.claude/settings.json - At least one workspace exists
Exits 0 if all checks pass, 1 if any fail. Each failure includes a concrete fix instruction.
ctask doctor also reports seed directory status: [INFO] if the CTASK_SEED_DIR / CTASK_SEED_PROJECT_DIR variable is unset (built-in defaults will be used), [PASS] if the variable is set and the path exists, [FAIL] if the variable is set but the path is missing. Only the configured-but-missing state counts as a failure.
Example output:
[PASS] Workspace root exists: C:\Users\Warren\ai-workspaces
[PASS] Default agent found: claude
[PASS] Status line helper found: C:\Users\Warren\AppData\Local\ctask\bin\ctask-statusline.sh
[PASS] Claude Code status line configured
[PASS] Workspaces found: 5 tasks (2 archived)
[INFO] General seed directory: not configured (using built-in defaults)
[INFO] Project seed directory: not configured (using built-in defaults)
[INFO] CTASK_PROJECT_ROOT: not set (projects discovered under C:\Users\Warren\ai-workspaces\projects)
5 checks passed, 0 failed
ctask delete
Permanently remove a workspace directory.
ctask delete <query> [flags]
Flags:
| Flag | Short | Default | Description |
|---|---|---|---|
--force |
-f |
off | Skip confirmation prompt |
--all |
-a |
off | Include archived workspaces in query resolution |
Examples:
ctask delete old-task
ctask delete --force old-task
ctask delete --all --force archived-task
Safety:
- Confirmation is required by default.
--forceskips it. - If the workspace has an active session (running in another terminal), deletion is refused even with
--force. Exit the session first. - If the workspace is the most recently updated one, a note is printed before confirmation.
Query Resolution
Commands that take a <query> argument (resume, open, info, archive, delete) resolve workspaces in this order:
- Exact directory name match (e.g.
2026-04-06_auth-bug) - Exact slug match (e.g.
auth-bug) - Case-insensitive substring match (e.g.
auth)
If multiple workspaces match, all matches are printed and the command exits. If none match, an error is printed.
Archived workspaces are excluded from matching by default. Use --all where supported to include them.
By default, queries search $CTASK_ROOT (including its projects/ subdirectory) plus $CTASK_PROJECT_ROOT when that variable is set. Projects created without CTASK_PROJECT_ROOT are therefore discoverable from any shell without additional configuration. Custom CTASK_PROJECT_ROOT paths must be set (recommended: at user scope) in any shell where you want those projects findable.
Environment Variables
ctask exports these into every child session:
| Variable | Description |
|---|---|
CTASK_TASK |
Task slug |
CTASK_MODE |
Execution mode (local) |
CTASK_ROOT |
Resolved workspace root path |
CTASK_WORKSPACE |
Full workspace path |
CTASK_CATEGORY |
Category name |
CTASK_TYPE |
task or project |
CTASK_LAUNCH_DIR |
Project subdirectory (v0.5); empty for tasks and pre-v0.5 projects |
Configure ctask behavior with:
| Variable | Default | Description |
|---|---|---|
CTASK_ROOT |
%USERPROFILE%\ai-workspaces (Windows) / ~/ai-workspaces (Unix) |
Workspace root directory |
CTASK_AGENT |
claude |
Default agent command |
CTASK_PROJECT_ROOT |
(none) | Workspace root for projects. When set, project workspaces are created directly under this path (no doubled projects/ segment unless -c is passed). |
CTASK_SEED_DIR |
%APPDATA%\ctask\seed\ (Windows) / ~/.config/ctask/seed/ (Unix) |
General user seed directory copied into every new workspace. |
CTASK_SEED_PROJECT_DIR |
%APPDATA%\ctask\seed-project\ (Windows) / ~/.config/ctask/seed-project/ (Unix) |
Project seed directory copied only for --project workspaces (overlay on top of the general seed). |
Concurrency and safety
ctask v0.4 protects workspaces from conflicts when multiple sessions (or manual file edits) touch the same workspace.
Session lease
Each active ctask resume, open, last, or new writes a lease file at <workspace>/.ctask/session.json identifying the ctask process, hostname, user, agent, mode, and a heartbeat timestamp. A background goroutine updates the heartbeat every 30 seconds.
On session start, if a fresh lease already exists (heartbeat within 60 seconds), ctask warns:
[ctask] This workspace has an active session:
Session: <id>
Host: <host>
Agent: <agent>
Started: <timestamp> (<elapsed> ago)
Last seen: <seconds> ago
Opening a second session may cause conflicts.
Continue anyway? [y/N]
If the user answers y, the second session proceeds without writing its own lease (see "Known limitation: coexisting sessions" below). If the lease is older than 60 seconds (crash, lost connection), ctask cleans it up silently and proceeds.
Metadata write lock
All ctask-owned file writes (task.yaml, logs/sessions.log, .ctask/session.json, .ctask/manifest-start.json, .ctask/last-session-summary.json) are serialized through <workspace>/.ctask/write.lock. The lock is held for the duration of one write only. If the lock cannot be acquired within 2 seconds, the write is skipped with a warning rather than blocking.
Stale-workspace detection
On session start, ctask compares the current workspace state against the end-state recorded by the previous session's summary. If anything changed outside a ctask session (another machine, manual edits), ctask warns:
[ctask] Workspace modified since last session ended:
Last session: <timestamp> (<host>, <agent>)
Modified since then:
notes.md (modified)
output/report.md (new file)
These changes were not made during a ctask session.
Review before continuing? [Y/n]
Press Enter (or y) to proceed. Press n to exit without launching.
This check is skipped silently for workspaces that have never completed a v0.4 session (no last-session-summary.json).
Session handoff summary
At end of session, ctask writes <workspace>/.ctask/last-session-summary.json containing:
session_id,hostname,agent,modestarted_at,ended_at,duration_secondsfiles_added,files_modified,files_deletednotes_updatedend_manifest(snapshot of workspace file list at session end -- used by the stale-workspace detector)
The next session prints a short orientation banner from this file:
[ctask] local :: api-cleanup
[ctask] ~/ai-workspaces/general/2026-04-21_api-cleanup
[ctask] Last session: 2026-04-21 14:30-15:45 (warren-desktop, claude)
[ctask] Changed: notes.md, output/plan.md
--force
--force on resume, open, and last suppresses both the active-session warning and the stale-workspace warning. It does not disable the metadata write lock or the session summary -- those are always active.
Use --force only for automation where the human has already decided to proceed.
Known limitation: coexisting sessions
When the user confirms "Continue anyway?" on an active-session warning (or passes --force), the second session runs without writing its own lease. This keeps the lease model simple (one lease file per workspace), but has two consequences:
- A third session attempt will only see the original lease. The second (coexisting) session is invisible to lease-based detection.
- If the original session exits and removes its lease before the coexisting session finishes, the coexisting session is unprotected for its remaining lifetime.
The metadata write lock still serializes all ctask-owned file writes regardless of session count, so no state corruption can occur. If you need stronger guarantees, exit the existing session before starting another one.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (multiple matches, not found, invalid args, doctor failure) |
| 2 | Missing required argument |
| 127 | Agent command not found |
Persistent Session Mode (tmux)
ctask v0.5.3+ supports an opt-in persistent session mode where workspace entry runs inside a deterministic per-workspace tmux session. Multiple terminals (local + SSH) attach to the same session, the agent and shell state survive terminal disconnection, and the v0.4 lifecycle protections continue to apply with one ctask process owning the workspace lifecycle while terminal connections come and go.
Enabling persistent mode
export CTASK_SESSION_MODE=persistent # in ~/.bashrc or equivalent
When unset or set to direct, ctask behaves as in v0.5.2 (no behavior change).
Persistent mode requires:
- tmux 3.0+ on PATH (install via
apt install tmux,brew install tmux,pacman -S tmux, ordnf install tmux) - An interactive terminal (over SSH, use
ssh -t) - Not running inside an existing tmux session
- A Unix-like host or WSL — native Windows is not supported (use WSL)
Three entry paths
When you run a persistent-mode entry command (new, resume, last, open, or attach), ctask picks one of three paths:
- Owner-create — no tmux session exists for this workspace yet. The command behaves like the direct path but launches the agent inside a new tmux session named
ctask-<category>-<slug>-<hash6>. - Passive reattach — a tmux session exists and a fresh local lease is heartbeating. The command attaches the user's terminal to the existing session and exits when the user detaches. No lease writes, no manifest, no finalize — the original ctask owner is still managing the workspace.
- Adopted reattach — a tmux session exists but the lease is missing, stale, or from another host (the original owner died). The command transfers ownership to itself, captures a fresh start manifest, starts heartbeating, attaches the terminal, and runs finalize when the session ends.
ctask attach <workspace>
ctask attach always uses tmux regardless of CTASK_SESSION_MODE. Useful when you have not enabled persistent mode globally but want tmux for one workspace, or when shell scripts need unambiguous behavior.
ctask attach promptvolley-v3
The same three paths apply.
--direct bypass flag
new, resume, last, and open accept --direct to bypass persistent mode for one invocation. When a persistent tmux session exists for the workspace, ctask prompts:
A persistent tmux session exists for this workspace:
ctask-projects-promptvolley-v3-a8f3c2
Opening a direct-mode shell may create conflicting workspace activity.
The recommended path is:
ctask attach promptvolley-v3
Continue with --direct anyway? [y/N]
--direct is a no-op under direct mode (allows scripts to use it defensively).
Doctor
ctask doctor reports:
[INFO] Session mode: persistent
[INFO] tmux found: tmux 3.4 (/usr/bin/tmux)
or, on misconfiguration:
[INFO] Session mode: persistent
[FAIL] tmux not found on PATH
Fix: install tmux 3.0+ (apt/brew/pacman/dnf), or unset CTASK_SESSION_MODE
Workflow examples
Local development
export CTASK_SESSION_MODE=persistent
ctask new --project promptvolley-v3
# -> workspace created, tmux session ctask-projects-promptvolley-v3-a8f3c2 started, attached.
# Detach with Ctrl-B d. Terminal returns; tmux session keeps running.
ctask resume promptvolley-v3
# -> passive reattach. Same Claude Code session, scrollback intact.
Remote access via SSH
ssh -t warren-desktop # -t is required
ctask resume promptvolley-v3 # -> passive reattach (concurrent with desktop)
Native Windows note
Persistent mode is not supported on native Windows (PowerShell). Run ctask under WSL and install tmux there.