Move a session from your laptop
How a coding agent on a laptop (Claude Code, Codex, OpenCode, Hermes or OpenClaw) copies its own session onto a NoClick agent, with whatever its work needs (code, files, secrets, CLIs, tools, skills, memory), so the copy carries on where it left off.
You are a coding agent in a session on someone's machine. They pasted a prompt asking you to move this session to NoClick, with a move code (nc_move_...). This page is how you do it. Read all of it as written, in your shell (curl -fsSL the page): a web fetch tool hands you a summary, which drops the commands and paths below, and the move's API answers only those.
A move makes a copy: a NoClick agent with this conversation, the code you're working on, and whatever else the work runs on. The laptop keeps its own session.
The goal
The copy carries on exactly where you left off, with everything it needs and nothing it doesn't. Its first turn should be able to run the command you'd run next, with the same tools, CLIs, logins and files, without the person reconnecting anything.
So the question for each thing on this machine is: would the copy reach for this? Not "is it on the laptop", but "did this work use it, or will it". You know the work; NoClick doesn't. NoClick gives you primitives (files, secrets, setup, a repository checkout, tool servers, skills, memory) and you decide what goes into them.
The rules
- Never print a secret. Not the move's key, not a token, not a config file holding one. Read values in code, send them straight to NoClick, and never echo them: your transcript moves too, and a person reads it.
- Never send a made-up, placeholder or guessed value. A secret you can't read (empty, missing, in a keychain, behind a prompt) isn't carried: name it for the person and leave it out. NoClick checks a model key with its provider as it lands, and a guessed one only fails.
- Ask before you carry a secret. List every secret you mean to carry, by name, what it is and where it goes in the copy, never its value, and wait for the person's yes in the session. NoClick refuses a secret the yes didn't name.
- Ask when you're unsure. A file outside the project, a credential that might be someone else's, a huge folder: ask rather than guess.
- Name what stays behind. Some things can't move. Tell the person what and why (your note says so too).
- Upload your session last, just before you finish, so it holds as much of the conversation as it can.
1. Tell the person, then redeem the code
Say in one or two lines what you're about to do, then redeem the code. It works once, so redeem it only once. The answer is the move's own key, and nothing else: it goes straight into a file only you can read, and you never print that file (no cat, echo or head of it), only read it inside a command, as the script below does:
mkdir -p ~/.noclick-move && chmod 700 ~/.noclick-move
echo https://api.noclick.com > ~/.noclick-move/api # or the API the prompt names
(umask 077; curl -sS --fail-with-body -X POST "$(cat ~/.noclick-move/api)/v1/moves/redeem" \
-d code=nc_move_PASTE_YOURS -d host="$(hostname -s)" -o ~/.noclick-move/key) \
|| { cat ~/.noclick-move/key; rm -f ~/.noclick-move/key; }If it fails, the file holds the refusal instead of a key, and the command prints it and removes the file: a 404 is a code that doesn't exist, a 410 says whether the code was already used, has expired or was called off. Ask the person for a fresh prompt. A used code never gives its key again, so if you lose the key file, a fresh prompt is the only way on.
The key reaches this move and nothing else, and stops working after an hour or when you finish. Every call below goes through one small script, so the key never shows:
cat > ~/.noclick-move/nc <<'EOF'
#!/bin/sh
# nc METHOD PATH [curl options]: one call to this move, with its key.
method=$1; path=$2; shift 2
exec curl -sS --fail-with-body -X "$method" -H "Authorization: Bearer $(cat ~/.noclick-move/key)" \
"$(cat ~/.noclick-move/api)/v1/moves/current$path" "$@"
EOF
chmod +x ~/.noclick-move/nc
~/.noclick-move/nc GET ""Each answer is the move as it stands: no value you sent is ever in it. It says where the agent lands (lands_as: its name, its harness and cwd, the folder it will work in on NoClick), what has arrived so far, the limits, and endpoints: every call of the move, one line each. A refused call answers with a message and a next_step: do what the step says. A call to a path the move doesn't have answers with its calls too.
2. Work out who and where you are, and say where you move from
You know which harness you run on. If you need to check, list the environment's variable names (never their values):
env | cut -d= -f1 | grep -iE '^(CLAUDECODE|CLAUDE_CODE|CODEX|OPENCODE|HERMES|OPENCLAW)' | sort -uA harness whose home was moved says so in a variable (CLAUDE_CONFIG_DIR, CODEX_HOME, XDG_DATA_HOME, HERMES_HOME, OPENCLAW_STATE_DIR): print that one, a path, with echo.
Then find your own session, the one this conversation is being written to. Your move code is in it, since the prompt that asked for the move is one of its messages, so search for the code:
| Harness | Its home | Your session |
|---|---|---|
| Claude Code | ~/.claude (or $CLAUDE_CONFIG_DIR) | grep -l nc_move_YOURS ~/.claude/projects/*/*.jsonl: one projects/<folder slug>/<session id>.jsonl |
| Codex | ~/.codex (or $CODEX_HOME) | grep -rl nc_move_YOURS ~/.codex/sessions: one sessions/YYYY/MM/DD/rollout-...-<session id>.jsonl |
| OpenCode | ~/.local/share (or $XDG_DATA_HOME) | opencode/opencode.db; the session is SELECT session_id FROM part WHERE data LIKE '%nc_move_YOURS%' LIMIT 1 |
| Hermes | ~/.hermes (or $HERMES_HOME) | state.db; the session is SELECT session_id FROM messages WHERE content LIKE '%nc_move_YOURS%' LIMIT 1 |
| OpenClaw | ~/.openclaw (or $OPENCLAW_STATE_DIR) | agents/main/agent/openclaw-agent.sqlite; the session is SELECT session_id FROM transcript_events WHERE event_json LIKE '%nc_move_YOURS%' LIMIT 1 |
Note the folder the session works in (for Claude Code, the folder it was started in: its slug is that path with symlinks resolved, pwd -P, so macOS's /var/ is /private/, and every character but letters and digits made -). Where $CLAUDE_CONFIG_DIR is set, every ~/.claude path in this guide is under it instead, ~/.claude.json included.
Then say where you move from. harness is one of claude_code, codex, opencode, hermes, openclaw:
~/.noclick-move/nc PUT /source -H 'Content-Type: application/json' -d '{
"harness": "claude_code", "host": "'"$(hostname -s)"'", "cwd": "/Users/ana/code/acme",
"session_id": "6f1e2d3c-...", "cli_version": "2.1.295", "title": "Fixing webhook retries"}'Add "model" with the id your own requests name, read from your session, never from an example: Claude Code writes it on each reply in the session file (message.model), and through a gateway it's the gateway's id (through OpenRouter, anthropic/claude-sonnet-4.5, say).
3. Work out what the session really used
Before you carry anything, look at what this work actually touched. The best evidence is your own transcript: the commands you ran, the tools you called, the files you read and wrote, the errors you hit. Then the machine around it, for example:
- The code:
git status,git remote -v, the branch, uncommitted and untracked work,git count-objects -vHfor its size. - Files outside the repo the work read: a spec, a dataset, a local notes file.
- Environment: the variables the project reads (its
.env, its code'sos.environ/process.env), and the ones your commands used. - CLIs: the commands you ran that aren't part of the base system (
gh,aws,stripe,vercel,psql,linear...), how you installed them, and how each signs in (gh auth status,aws configure list,stripe config --list, its config file under~/.config/...or~/.<tool>). - The model you run on, and where its key really comes from. Work out where your harness sends its requests (its base URL: a variable such as
ANTHROPIC_BASE_URLorOPENAI_BASE_URL, or the provider its config names) and what it signs them with (whichever token variable or config entry it actually reads:ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN, a settings file's key helper, a config'senv_key...), or whether it runs on a plan you signed in to. The key belongs to the provider it works at: requests going to openrouter.ai run on an OpenRouter key, whatever the variable is called. - Tool servers (MCP) your harness has: ask the harness itself where it can (
claude mcp list,codex mcp list), which honours a moved home and lists every server; else its config file (Claude Code:~/.claude.jsonunder its config dir and the project's.mcp.json; Codex:~/.codex/config.toml; OpenCode:opencode.json; Hermes:~/.hermes/config.yaml; OpenClaw:openclaw.json). Then see which ones you actually called. - Your skills and custom commands (Claude Code:
~/.claude/skills/,~/.claude/commands/; Codex:~/.codex/skills/,~/.codex/prompts/; OpenCode:~/.config/opencode/; Hermes:~/.hermes/skills/; OpenClaw:~/.openclaw/skills/). - What you learned: your harness's memory (Claude Code: the memory folder its instructions name,
projects/<slug>/memory/under its config dir:MEMORY.md, its index, and the files it links; Hermes:~/.hermes/memories/; OpenClaw: the workspace'sMEMORY.mdandmemory/). - Your standing instructions: a user-level
CLAUDE.md,AGENTS.mdorSOUL.md, what you're told for every project.
These are examples, not a checklist. A project might lean on a tool this page never names; carry that too. And skip what the work never used, however much is on the machine: the copy should be lean.
Where the model key is, from evidence rather than a guess. This prints where your requests go (scheme and host only) and the names of the key and token variables that are set, never a value:
env | sed -nE 's#^([A-Za-z_][A-Za-z0-9_]*_BASE_URL)=([A-Za-z][A-Za-z0-9+.-]*://)([^/?#@]*@)?([^/?#:]*).*#\1 \2\4#p'
env | cut -d= -f1 | grep -iE 'key|token' | sort -u | while read -r name; do [ -n "$(printenv "$name")" ] && echo "$name is set"; doneA variable that's set but empty is no key. The key is the token your harness sends to that host: through openrouter.ai, Claude Code's ANTHROPIC_AUTH_TOKEN is an OpenRouter key, carried as {"model": "openrouter"}; with no base URL set, ANTHROPIC_API_KEY goes to Anthropic. A harness that reads its key from a config file or a key helper has it there.
Which tool servers sign in, and how, from their config, never from claude mcp list alone (it shows no headers). For Claude Code this prints each server it has here (your user config, this project's own entries in it, the project's .mcp.json) with its address and the NAMES of the headers its config sends, never a value:
python3 - <<'EOF'
import json, os
from urllib.parse import urlsplit
home = os.environ.get("CLAUDE_CONFIG_DIR") or os.path.expanduser("~")
here = {os.getcwd(), os.path.realpath(os.getcwd())}
groups = []
for path in (os.path.join(home, ".claude.json"), os.path.expanduser("~/.claude.json"), ".mcp.json"):
try:
config = json.load(open(path))
except (OSError, ValueError):
continue
groups.append(config.get("mcpServers") or {})
groups += [p.get("mcpServers") or {} for d, p in (config.get("projects") or {}).items() if d in here]
seen = set()
for servers in groups:
for name, server in servers.items():
if name in seen:
continue
seen.add(name)
url = urlsplit(server.get("url") or "")
where = f"{url.scheme}://{url.hostname}{url.path}" if url.hostname else (server.get("command") or "?")
print(name, where, "headers:", ", ".join(sorted(server.get("headers") or {})) or "none")
EOFOther harnesses keep the same in their own config: Codex's [mcp_servers.<name>] in ~/.codex/config.toml (http_headers, env_http_headers, bearer_token_env_var), OpenCode's mcp in opencode.json (headers). Read the names there the same way, never a value. Then for each server the work used:
- Its config sends a header that signs in (
Authorization, an API key header): carry that header's whole value as a secret and name it in the header (step 10). Its tools then work in the copy with the same login. Never move it as"sign_in": "browser": the copy would wait for a sign-in the person never needed. - No login in its config, and it asks for one (
claude mcp listsays it needs authentication), or a claude.ai connector:"sign_in": "browser". - No login in its config, and it connects: it moves as it is, its URL and nothing else.
The project's own .claude/, CLAUDE.md, AGENTS.md, .mcp.json and skills come with the code. Only what lives outside the repo needs carrying on its own.
4. Decide what moves, and what stays
Most things move as one of the primitives below. Some don't, and you name them for the person instead:
- What runs only here: a tool server that's a local command or on
localhost, a hook that callsosascript, a path under/Users/the work depends on, a database on this machine. Say what it is; the person decides what replaces it.... - Logins that live in the person's browser: a connector from their claude.ai account (Claude Code lists them as
claude.ai …inclaude mcp list), or any remote tool server you signed in to in a browser (OAuth). Their logins don't move, but the servers do: carry each one the work used as needing sign-in (step 10), and the person signs in to it once on NoClick. - A plan sign-in: if you run on a Claude or ChatGPT plan rather than an API key, don't copy its tokens. Get the person the one-tap sign-in instead (step 9).
- Someone else's credential: a token that's plainly a teammate's or a production admin's, when the work doesn't need that reach. Ask.
When something could go either way, ask the person, briefly, and go with their answer.
5. Ask the person before carrying secrets
List every secret you mean to carry: its name (yours to choose, like github-token), what it is, and where it goes in the copy. Never the value. For example:
To move with everything it needs, I'll carry these secrets (values never shown):
model-key: the OpenRouter key I run on, as the copy's model keygithub-token: your GitHub token (fromgh), inGITHUB_TOKEN, and for cloning and pushing acme/retrylinear-token: the Linear API key the linear tool server signs in withaws-config: your AWS credentials file, at~/.aws/credentialsOK to carry them?
Once they say yes, record it, the way they read it:
~/.noclick-move/nc PUT /consent -H 'Content-Type: application/json' -d '{"confirmed": true, "secrets": [
{"name": "model-key", "kind": "OpenRouter API key", "to": "the model key the copy runs on"},
{"name": "github-token", "kind": "GitHub token", "to": "env GITHUB_TOKEN, and the checkout"},
{"name": "linear-token", "kind": "Linear API key", "to": "the linear tool server'"'"'s login"},
{"name": "aws-config", "kind": "AWS credentials file", "to": "~/.aws/credentials"}]}'If they say no to one, leave it out and name it in your note. This replaces the list you sent before, so send the whole list each time.
6. Carry the secrets
Each secret is a value and where it goes in the copy (to):
{"env": "NAME"}: an environment variable in the copy's shell.{"file": "~/.aws/credentials", "mode": "600"}: a file the copy's sandbox writes as it starts (absolute, or under~/; mode defaults to600).{"model": "openrouter"}: the model key the copy runs on, named by the provider it works at (anthropic,openai,openrouter,gemini...). NoClick checks it with that provider as it lands, and refuses it in the provider's own words if the provider does; then it makes it a connection of the account's (reusing one that already holds the same key) and the copy runs on it. If you can't read the key, leave it out and say so: the copy runs on the person's plan once they tap its sign-in (step 9), or on a key their account already holds.{}: nowhere of its own, for a secret only a tool server's header or the checkout refers to by name.
Read each value in code and send it without printing it:
python3 - <<'EOF' | ~/.noclick-move/nc PUT /secrets/model-key -H 'Content-Type: application/json' --data-binary @-
import json, os
# For example, Claude Code sending its requests to OpenRouter (ANTHROPIC_BASE_URL=https://openrouter.ai/api) signs
# them with ANTHROPIC_AUTH_TOKEN: that's an OpenRouter key. Read what your harness really reads; never fill a gap.
token = os.environ.get("ANTHROPIC_AUTH_TOKEN")
assert token, "no key to carry: name it for the person instead"
print(json.dumps({"value": token, "to": {"model": "openrouter"}}))
EOF
python3 - <<'EOF' | ~/.noclick-move/nc PUT /secrets/github-token -H 'Content-Type: application/json' --data-binary @-
import json, subprocess
token = subprocess.run(["gh", "auth", "token"], capture_output=True, text=True, check=True).stdout.strip()
print(json.dumps({"value": token, "to": {"env": "GITHUB_TOKEN"}}))
EOF
python3 - <<'EOF' | ~/.noclick-move/nc PUT /secrets/aws-config -H 'Content-Type: application/json' --data-binary @-
import json, os
text = open(os.path.expanduser("~/.aws/credentials")).read()
print(json.dumps({"value": text, "to": {"file": "~/.aws/credentials"}}))
EOFValues in the project's .env go the same way, one secret each ({"env": "DATABASE_URL"}). A model key belongs in {"model": ...}, never in the environment.
7. Give the copy the same CLIs
The copy's sandbox starts as a plain Linux machine with your harness. Whatever CLIs and packages the work used, it installs as it starts, after your secrets' files are written and with your secrets in its environment:
~/.noclick-move/nc PUT /setup -H 'Content-Type: application/json' -d '{"commands": [
"curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && echo \"deb [signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main\" | sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null && sudo apt-get update -qq && sudo apt-get install -y -qq gh",
"npm install -g @linear/cli",
"pip install --quiet awscli"]}'Use the installer each tool documents for Linux. Keep each command quiet and idempotent: it runs every time the sandbox starts, and one that fails stops the start, naming its step. A CLI that signs in through a file (gh's ~/.config/gh/hosts.yml, AWS's ~/.aws/credentials) gets that file as a secret (step 6); one that reads a variable gets it in the environment.
8. The code
If you work in a git repository, the copy checks it out on a branch holding your uncommitted and untracked work (what .gitignore leaves out stays out). This script commits that work on HEAD with a temporary index, so the person's working tree, index and stash never change, and prints the commit:
cat > ~/.noclick-move/commit <<'EOF'
#!/bin/sh
# commit: the uncommitted work, committed on HEAD with a temporary index; prints the commit.
set -e
cd "$(git rev-parse --show-toplevel)"
export GIT_INDEX_FILE="$HOME/.noclick-move/index"
git read-tree HEAD
git add -A
git commit-tree "$(git write-tree)" -p HEAD -m "Uncommitted work moved from $(hostname -s)"
rm -f "$GIT_INDEX_FILE"
EOF
chmod +x ~/.noclick-move/commitBefore you push anything, check the repository's size (git count-objects -vH). A huge one, or one with secrets in its history, is something to raise with the person first. Then one of two ways:
The session's own remote, best when it has one you can push to: push the commit to a new branch there with your own git, then have the copy check the remote out at that branch, signed in with a carried secret:
BRANCH="noclick/$(hostname -s)-$(date +%Y%m%d)"
git push -q origin "+$(~/.noclick-move/commit):refs/heads/$BRANCH" && echo "Pushed to $BRANCH"
~/.noclick-move/nc PUT /checkout -H 'Content-Type: application/json' -d '{
"url": "https://github.com/acme/retry.git", "branch": "'"$BRANCH"'", "secret": "github-token"}'url is the remote's https URL with no login in it (an ssh remote's https twin). secret names the carried secret whose value signs the clone and pushes in; username is git's username with it (x-access-token if omitted, which GitHub takes; GitLab takes oauth2). A public repository needs no secret. Tell the person which branch you pushed to their remote.
A NoClick copy of the code, for a folder with no remote or one you can't push to: NoClick makes a repo for it, and this script pushes the commit there:
cat > ~/.noclick-move/push <<'EOF'
#!/bin/sh
# push [repo options as JSON]: the uncommitted work pushed to the move's NoClick repo.
# The push URL holds a token: it's read from repo.json here and never printed.
set -e
body=${1-}
[ -n "$body" ] || body='{}'
COMMIT=$("$HOME/.noclick-move/commit")
cd "$(git rev-parse --show-toplevel)"
"$HOME/.noclick-move/nc" POST /repo -H 'Content-Type: application/json' -d "$body" -o "$HOME/.noclick-move/repo.json"
field() { python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))[sys.argv[2]])' "$HOME/.noclick-move/repo.json" "$1"; }
GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=remote.noclick.url GIT_CONFIG_VALUE_0="$(field url)" GIT_TERMINAL_PROMPT=0 \
git push -q noclick "+$COMMIT:refs/heads/$(field branch)"
echo "Pushed $(printf %.7s "$COMMIT") to $(field name), branch $(field branch); the agent works in $(field cwd)."
EOF
chmod +x ~/.noclick-move/push
~/.noclick-move/pushThe push URL holds a token for ten minutes: never print repo.json or put its URL in a command. Run the script again for a fresh one. Pass it '{"name": "...", "branch": "..."}' the first time to choose the repo's name or the branch (the folder's name and laptop/<host>-<date> otherwise); after that the move keeps them.
Either way, check the push printed Pushed ...: the copy checks its code out on that branch, so the finish refuses while nothing is there. A folder that isn't a git repository has no code to push: carry its files instead.
9. A plan sign-in
If you run on a Claude or ChatGPT plan rather than an API key, get the person the one-tap sign-in to that plan in NoClick, and show them the link. It's only for the plan your own harness signs in with, and only when that plan runs the copy's harness (a Claude plan runs only Claude Code); otherwise it's refused, and you carry the key you run on:
~/.noclick-move/nc POST /plan_linkIts url opens the provider's own sign-in; the plan's tokens never leave this machine. Once they've signed in, the copy runs on it. If they'd rather use an API key, carry it as a {"model": ...} secret instead.
10. Files, tool servers, skills, memory and instructions
Files outside the repo the work uses land in /workspace/:
~/.noclick-move/nc PUT '/files?path=notes/retry-spec.md' --data-binary @"$HOME/notes/retry-spec.md"Tool servers (remote MCP servers your config names, a public https URL): the copy has their tools. A header that signs in names a carried secret; any other header is plain text:
~/.noclick-move/nc PUT /mcp_servers/linear -H 'Content-Type: application/json' -d '{
"url": "https://mcp.linear.app/mcp", "headers": {"Authorization": {"secret": "linear-token"}}}'Carry the header's whole value as the secret (Bearer lin_... if that's what the config sends). A login written into the URL, or into a header as text, is refused: the server's definition isn't sealed.
A server whose config holds no login and that asks for one in a browser (a claude.ai connector, or one you signed in to with OAuth) moves with its URL and "sign_in": "browser", and no headers; one whose config sends a header never does (step 3). Nothing secret moves, so it needs no yes from the person, but list it for them: the copy has it as a tool needing sign-in, and they tap Connect on NoClick once to sign in to it there.
~/.noclick-move/nc PUT /mcp_servers/notion -H 'Content-Type: application/json' -d '{
"url": "https://mcp.notion.com/mcp", "sign_in": "browser"}'Its URL is the server's own (from claude mcp list or your config). One that only answers through the person's claude.ai account can't be signed in to elsewhere: say so, and they connect the same app in NoClick instead.
Skills: each of your own skills as its folder (a tar, its SKILL.md at the top), kept as a NoClick skill attached to the copy. A custom command of yours moves as a skill too: a folder holding a SKILL.md that starts with name and description frontmatter, then the command's text.
tar czf - -C ~/.claude/skills/release-notes . | ~/.noclick-move/nc PUT /skills/release-notes --data-binary @-Memory: each file your harness's memory holds, sent whole under its file's name, with a line on what it's about. Don't write new memories from the conversation: the conversation moves on its own.
python3 - <<'EOF' | ~/.noclick-move/nc PUT /memories/deploy -H 'Content-Type: application/json' --data-binary @-
import json, os
home = os.environ.get("CLAUDE_CONFIG_DIR") or os.path.expanduser("~/.claude")
text = open(os.path.join(home, "projects/-Users-ana-code-acme/memory/deploy.md")).read()
print(json.dumps({"content": text, "description": "How this repo deploys"}))
EOFInstructions: your standing instructions become the copy's:
~/.noclick-move/nc PUT /instructions -H 'Content-Type: text/plain' --data-binary @"$HOME/.claude/CLAUDE.md"11. The handoff note
Write where you left off: what you were doing, what's done, what's next, anything half-finished, and what stayed behind and why. The copy reads it first, privately, with where everything now lives.
cat > ~/.noclick-move/note.md <<'EOF'
Working on: exponential backoff in retry().
Done: the backoff, and its test for the first two retries.
Next: a test for the third retry; then the person reviews.
Stayed on the laptop: the local postgres (the copy uses DATABASE_URL's staging database instead).
EOF
~/.noclick-move/nc PUT /note -H 'Content-Type: text/plain' --data-binary @"$HOME/.noclick-move/note.md"12. The session
Last, upload your session's own files: a tar of their paths relative to your harness's home, exactly as the harness keeps them. NoClick reads the session at once and says how many messages it found, or why it couldn't. Before it's kept, every secret you carried, and anything shaped like a key, is cut out of it. Pass the session's id and the folder it works in:
# Claude Code: the session file you found in step 2
tar czf - -C ~/.claude projects/-Users-ana-code-acme/6f1e2d3c-....jsonl | ~/.noclick-move/nc PUT \
"/session?harness=claude_code&session_id=6f1e2d3c-...&cwd=/Users/ana/code/acme" --data-binary @-
# Codex: tar czf - -C ~/.codex sessions/2026/11/10/rollout-...jsonlA harness that keeps sessions in SQLite (OpenCode, Hermes, OpenClaw) goes as a consistent copy of its database, which NoClick reads only your session from, and deletes when the move ends:
mkdir -p ~/.noclick-move/store/opencode && python3 -c 'import sqlite3,os,sys; s=sqlite3.connect(os.path.expanduser(sys.argv[1])); s.backup(sqlite3.connect(os.path.expanduser(sys.argv[2])))' \
~/.local/share/opencode/opencode.db ~/.noclick-move/store/opencode/opencode.db && \
tar czf - -C ~/.noclick-move/store opencode/opencode.db | ~/.noclick-move/nc PUT \
"/session?harness=opencode&session_id=ses_...&cwd=/Users/ana/code/acme" --data-binary @-13. Finish, then check
~/.noclick-move/nc POST /finish -H 'Content-Type: application/json' -d '{}'The finish makes the copy step by step (steps: secrets, agent, space, files, tools, skills, memories, conversation) and answers with each one's status and arrived.carried: what it made of each secret, model key and tool server login, by name. Check that every step is done and that carried holds everything the person said yes to. The move's key ends with it, so remove the folder: rm -rf ~/.noclick-move.
If you carried no tool server and no skill, the finish first answers with a question instead of steps, and makes nothing yet: list yours (claude mcp list and your skills folder, or your harness's own), carry the ones the work used, and finish again; if the session really has none, say so: {"none": ["mcp_servers", "skills"]}.
If a step failed, its error says why: fix that and finish again, which picks up at that step. The harness the person chose on the Draft screen is theirs: a finish naming another is refused. If nothing runs your harness here:
- You carried a model key it doesn't run on (an OpenRouter key under Claude Code, say): the refusal names the harnesses that key does run. Pick one and finish again on it, with the model as the key's provider names it:
{"harness": "opencode", "model": "openrouter/anthropic/claude-sonnet-4.5"}. Don't ask for a plan the person doesn't run on. - You carried none: carry the key you run on (with the person's yes), or, if you run on a plan, get them its sign-in (step 9), then finish again.
You can also pass {"model": "...", "model_connection": "..."} when they name one.
Then tell the person, in a few lines, what moved, what stayed behind and why, which tool servers need their sign-in (Connect, on NoClick), and that the copy is on NoClick with this conversation. Everything the move carried is listed in the space's Resources and on the agent's profile, marked where it came from, with Remove.
14. How the copy checks it has everything
A good first message to the copy asks it to check, the way you would: run the next command of the work, list the CLIs it expected (gh --version, aws sts get-caller-identity), confirm each variable is set without printing it, call one tool of each tool server, and read back the plan from its note. Suggest that message to the person, written for this work.
With the noclick CLI
Where the noclick command is installed (noclick --version), noclick agents move <step> does each step without printing values:
noclick agents move redeem --code nc_move_PASTE_YOURS
noclick agents move source --harness claude_code --cwd . --session-id 6f1e2d3c-...
noclick agents move consent "github-token:GitHub token:env GITHUB_TOKEN" "anthropic:Anthropic key:model key" --yes
noclick agents move secret anthropic --from-env ANTHROPIC_API_KEY --to-model anthropic
noclick agents move secret github-token --from-env GITHUB_TOKEN --to-env GITHUB_TOKEN
noclick agents move secret aws-config --from-file ~/.aws/credentials --to-file ~/.aws/credentials
noclick agents move setup "npm install -g @linear/cli"
noclick agents move checkout --url https://github.com/acme/retry.git --branch noclick/ana-mbp --secret github-token
noclick agents move repo
noclick agents move file ~/notes/spec.md --to notes/spec.md
noclick agents move mcp linear --url https://mcp.linear.app/mcp --headers-file headers.json
noclick agents move skill ~/.claude/skills/release-notes
noclick agents move memory deploy --file ~/.claude/projects/-Users-ana-code-acme/memory/deploy.md --description "How this repo deploys"
noclick agents move note --file ~/.noclick-move/note.md
noclick agents move session --harness claude_code --home ~/.claude --cwd "$PWD" --session-id 6f1e2d3c-... projects/-Users-ana-code-acme/6f1e2d3c-....jsonl
noclick agents move finishThe repo step makes the work commit and pushes it to a NoClick copy for you; for the session's own remote, push the branch with your git and use checkout.