Repository navigation
feat(sessions): continue a Claude Code or Codex session in Base Code - #679
Conversation
`base44 import` uploads one session file to
POST /api/apps/{app_id}/imported/session-imports and prints the new
branch's link.
- `--current` finds the running session: CLAUDE_CODE_SESSION_ID, then
CODEX_THREAD_ID, then the newest Codex rollout recorded in this folder.
- A claude.ai session link runs `claude --teleport` in a temp clone of
this checkout's origin, drops the teleport exchange, and cleans up.
- `--file` uploads a file as is; no argument opens a picker.
- `--token <b44imp_…>` sends the one-time key through its own client, so
the stored login is never used, refreshed or cleared.
- Errors are plain messages an agent can relay, carry no request body,
and the link and key never reach telemetry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🚀 Package Preview Available!Install this PR's preview build with npm: npm i @base44-preview/cli@0.2.3-pr.679.8a2af8dPrefer not to change any import paths? Install using npm alias so your code still imports npm i "base44@npm:@base44-preview/cli@0.2.3-pr.679.8a2af8d"Or add it to your {
"dependencies": {
"base44": "npm:@base44-preview/cli@0.2.3-pr.679.8a2af8d"
}
}
Preview published to npm registry — try new features instantly! |
…ce_branch Base Code starts the new branch from GitHub, so a session imported with local-only work lands without it. For --current and the picker, the command now stops before uploading (the one-time key stays valid) when the checkout has uncommitted changes, unpushed commits, or a branch with no upstream or a deleted one. --skip-push-check imports anyway and says the local changes aren't included. A link (its changes live in the cloud session), --file, and runs outside a git checkout skip the check. The current branch goes with the upload as source_branch, left out when HEAD is detached; the backend starts the new branch from it when it's on GitHub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Remaining CLI changes to match the server side in base44-dev/apper#34732 (which this PR merges with). Already done here: the network-blocked message names only 1. 409 {"error":{"code":"branch_exists","message":"Base Code already has a branch for feature/x.","details":{"branch_name":"feature/x","branch_url":"/apps/<id>/editor/preview?branch=feature%2Fx"}}}Today it falls through to the server message with no link. Suggested: // ErrorEnvelopeSchema: add details: z.object({ branch_name: z.string(), branch_url: z.string() }).partial().optional()
if (code === "branch_exists" && details?.branch_url) {
return `Base Code already has ${details.branch_name}. Open it: ${new URL(details.branch_url, getBase44ApiUrl())}`;
}
if (code === "repository_not_connected") return "Connect this app to its GitHub repository in Base Code first.";2. "Continued on" when the import continued the session's branch ( imported.continued
? `✓ Imported ${n} messages from ${tool}. Continued on "${imported.branchName}".`
: `✓ Imported ${n} messages from ${tool} into branch "${imported.branchName}".` // as today3. Codex cloud: say it's not supported instead of "Couldn't find the current session" (
4. Wording of the network-blocked message (
Tests: |
…loud cases - 409 branch_exists prints the existing branch's link from details.branch_url; 409 repository_not_connected says to connect it. - A success with continued: true reads "Continued on <branch>". - CODEX_THREAD_ID with no rollout file is Codex cloud, which keeps the conversation on OpenAI's side: say it isn't supported. - The network message names the Claude cloud environment. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
All four are done in fd22d22:
Tests: there's a new case for each of 1–3, and Note: |
|
Rename A bare top-level Please:
base44-dev/apper#34732 already uses the new name: the Import session dialog's prompt now runs |
A bare top-level `import` reads as importing a project, which is what "Import" means in Base Code, and every other group is a plural noun. The help line drops "in a new branch": the import now continues on the session's own pushed branch when it can. No alias, since `import` never shipped. base44-dev/apper#34732's dialog prompt already uses the new name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Renamed in 12185af:
Also updated:
This and base44-dev/apper#34732 need to merge together, because the dialog's prompt runs |
|
Send an The upload ( In import { randomUUID } from "node:crypto";
const idempotencyKey = randomUUID(); // once per `sessions import` run
// in uploadSession(), on both the import-key ky.post and the getAppClient() post:
headers: { ...headers, "Idempotency-Key": idempotencyKey },Reuse the same value for any retry within the run, and never share it across runs. Two new 409 codes are worth a message in
|
|
Idempotency-Key: done in #682, since this PR was already merged.
All 29 tests in |
Note
Description
Adds
base44 sessions import, a command that continues an existing Claude Code or Codex session in a new Base Code branch. It uploads one session file toPOST /api/apps/{app_id}/imported/session-importsand prints the resulting branch link. The session can be the one the command runs inside (--current), a claude.ai cloud session pulled down withclaude --teleport, an explicit--file, or one picked interactively. Because Base Code starts the new branch from GitHub, the command refuses to upload a local session whose work isn't pushed yet, and it sends the current branch assource_branchso the new branch starts from the right place.Related Issue
None
Type of Change
Changes Made
base44 sessionscommand group (src/cli/commands/sessions/index.ts, registered inprogram.ts) with one subcommand,import(src/cli/commands/sessions/import.ts), taking[session-link],--current,--file <path>,--skip-push-checkand--token <key>; only one session source may be given at a time.src/core/session-import/sessions.ts):--currentresolvesCLAUDE_CODE_SESSION_IDto~/.claude/projects/*/<id>.jsonl, thenCODEX_THREAD_IDto its rollout file, else the newest~/.codex/sessions/*/*/*/rollout-*.jsonlwhosesession_metacwd is this folder. ACODEX_THREAD_IDwith no rollout file means Codex cloud, which is reported as not importable. The picker lists this folder's 20 newest Claude Code and Codex sessions labelled by first user message. JSONL files are streamed line by line, so large transcripts are never loaded whole.parseSessionLinkextracts thesession_…id (rejectingcshk_"Copy link" URLs);teleportSessionrunsclaude --teleport <id> -p "Reply with OK."inside a temp clone of this checkout'sorigin, strips the teleport exchange and the continued-from-another-machine note from the transcript, then deletes the clone and its~/.claude/projectsfolder.src/core/session-import/api.ts): multipart POST with a 25 MB client-side cap, 3-minute timeout, and a Zod snake_case-to-camelCase response transform. With--tokenthe request goes through a barekycall rather thanbase44Client, whose auth hook would replace the header and whose 401 handler would refresh and retry as the stored login.readGitStateinsrc/core/utils/git.ts): parsesgit status --porcelain --branchplusgit rev-parse --abbrev-ref HEADto detect uncommitted changes, unpushed commits, a missing upstream or a[gone]upstream, and stops before the upload so a one-time key stays valid for the re-run.--skip-push-checkimports anyway and says the local changes aren't included. Links,--fileand non-git folders skip the check. The branch is sent as thesource_branchform field, omitted when HEAD is detached.continued: truereports that Base Code continued on the session's own branch; a 409branch_existsprints the existingdetails.branch_url. Known statuses (401 with a key, 403, 404, 413, 429) and codes (unsupported_file,repository_mismatch,repository_not_connected) get actionable messages. Inside claude.ai (CLAUDE_CODE_REMOTE) a refused connection or non-JSON 403 explains thatapp.base44.commust be added to the environment's allowed domains.--tokennever reach telemetry.--jsonsupport: the import result plus the full URL is returned viaRunCommandResult.stdout; human status goes tooutroMessage.claudeBintest override (TestOverridesSchema,CLITestkit.givenClaudeBin,TestContext) so the teleport path can run a stub binary.docs/commands.mdand a CHANGELOG entry under Unreleased / Added.Testing
npm test)packages/cli/tests/cli/sessions_import.spec.tsadds 21 tests covering source selection and mutual exclusion, the--currentlookups for Claude Code and Codex, Codex cloud, the size cap,--tokenauth isolation and expiry, thebranch_existsandcontinuedresponses, telemetry redaction,--jsonoutput, the teleport flow against a stubbedclaudebinary, and the push-check branches (clean/dirty/never-pushed/ahead/detached/--file) against a real temp git repo. The build and test commands were not run in this environment; CIlint,typecheck,knipandpublish-previeware green, and thebuildandfunctions-compilerjobs were still in progress when this description was written.Checklist
docs/(AGENTS.md) if I made architectural changesAdditional Notes
The command sits under a
sessionsgroup rather than at the top level, leaving room for more session commands later. It isrequireAuth: falseso a one-timeb44imp_key works with no CLI login;ensureAuthruns only when no key is given. All session parsing, teleport and upload logic lives in the newsrc/core/session-import/module, leavingimport.tswith prompts and output only, per the core/cli split. The only new behaviour outside the command isreadGitState, added to the existingcore/utils/git.ts.🤖 Generated by Claude | 2026-10-28 09:25 UTC | 12185af