Skip to content

docs: TUI local-to-cloud handoff (CODE-1827) [auto-draft] - #417

Closed
rachaelrenk wants to merge 1 commit into
mainfrom
docs/CODE-1827-feature-draft
Closed

rachaelrenk wants to merge 1 commit into
mainfrom
docs/CODE-1827-feature-draft

Conversation

@rachaelrenk

Copy link
Copy Markdown
Contributor

Auto-drafted docs: TUI local-to-cloud handoff

Feature: TUI /handoff command — forks a local Warp Agent conversation in the terminal UI into an Oz cloud run.
Spec ID: CODE-1827
Spec PR: warpdotdev/warp#14208

This page was auto-drafted in ambient mode by the write-feature-docs skill (triggered by scan-new-specs). It is drafted only from PRODUCT.md plus behavior verified against warpdotdev/warp source. No content was derived from TECH.md.

  • New page: src/content/docs/platform/handoff/tui-local-to-cloud.mdx
  • Sidebar: added a placeholder entry to the Handoff group in src/sidebar.ts (marked // [TODO: docs reviewer — confirm placement]).

⚠️ Placement decision needed

scan-new-specs suggested the agent-platform section, but the repo already has a dedicated Handoff hub under platform/handoff/ (Overview, Local to cloud [GUI], Cloud to cloud, Snapshots). To keep handoff docs cohesive, this draft was placed alongside them at platform/handoff/tui-local-to-cloud.mdx. Reviewer should decide whether to:

  • keep it as a standalone page here, or
  • merge it into platform/handoff/local-to-cloud.mdx as a "From the terminal UI" section, or
  • move it under agent-platform/.

Docs outline (auto-generated)

The following outline was generated from the spec. @harryalbert: please review and check off each item, or leave a comment with corrections.

Content structure

  • H1: Handoff from the terminal UI to the cloud
  • Opening paragraph: /handoff forks a local Warp Agent conversation in the terminal UI into an Oz cloud run, preserving the transcript and offering explicit next steps.
  • Key features: keyboard-first handoff, preserved local conversation, context transfer (history + snapshot + images), explicit next steps.
  • How it works: forking model, always-remote Oz execution, what carries over (forked conversation, workspace snapshot, pending images), and how the initial cloud prompt is chosen.
  • ## Handing off a conversation to the cloud: prerequisites, start the handoff, configure environment/model, confirm and track, after-handoff choices.
  • Related pages: Handoff overview, Local to cloud (GUI), Snapshots, Environments, Cloud-synced conversations.

Items needing engineer verification ⚠️

  • Availability gating / settings path — the exact user-facing setting(s) that gate TUI handoff. Code gates on AISettings::is_cloud_handoff_enabled plus privacy/account/AI-availability/native-platform checks; the GUI flow requires Settings > Privacy > Store AI conversations in the cloud. Confirm whether the same setting and/or a separate handoff toggle applies in the terminal UI.
  • Product surface name — spec calls this "Warp's headless TUI"; draft uses "terminal UI." Confirm the preferred term ("Oz CLI", "terminal UI", etc.).
  • Ghost-text wording — exact ghost text shown after /handoff is inserted with no argument (spec says it communicates the prompt is optional; exact string not verified).
  • Third-party CLI agent coverage — whether /handoff is available in third-party CLI agent (Claude Code / Codex) terminal sessions, or Warp Agent only (GUI local-to-cloud is Warp Agent only).
  • Screenshots — computer use was unavailable, so three screenshot placeholders remain (acceptance card, selector page). See [TODO: docs reviewer — screenshot needed] comments in the MDX.
  • Final URL / placement — see the placement decision above.

Verified from codebase ✅

Verified in crates/warp_tui/src/handoff/{model,block,session}.rs (warpdotdev/warp):

  • Slash command is /handoff and is TUI-only (no & entry point); record_static_slash_command_accepted("/handoff", ...), HandoffEntryPoint::SlashCommand, HandoffSurface::Tui.
  • Card title: Hand off to cloud. Exactly two editable values: Environment and Model; execution always remote on the Oz harness.
  • Acceptance key hints: Enter to hand off, Ctrl + E to edit, Ctrl + C to cancel.
  • Configuring key hints: Enter to accept, Tab/← → to navigate, Esc to go back; two searchable selector pages (environment, then model).
  • Incompatible model handling: label suffix (incompatible); confirmation blocked until a compatible model is chosen; never silently substitutes.
  • No-environment state: message "A cloud environment is required to hand off this conversation."; Enter opens https://oz.warp.dev/environments (OZ_ENVIRONMENTS_URL), R refreshes, Ctrl + C cancels; auto-transitions when an environment appears.
  • Empty-source behavior: /handoff alone rejected ("Nothing to hand off — start a conversation or add a prompt."); /handoff <prompt> starts a fresh cloud run without a fork.
  • Guardrails: long-running command active → rejected; active/blocked child conversation → rejected.
  • Commit phase: Creating cloud run… progress; Ctrl + C consumed; local input blocked.
  • Created card: Enter opens the run, C continues locally (forked conversations only), N starts a new conversation; opening the run launches its Oz URL in the browser.
  • Continue-locally banner text: "Conversation forked to cloud; continuing locally" with the cloud-run link on the next row.
  • No-prompt synthesized prompts (from PRODUCT.md): "Continue. Apply the workspace changes from my previous session." / "Continue" / "Apply the workspace changes from my previous session." / none.
  • Snapshot upload failure is non-fatal; conversation history is still handed off.

Reviewers

/cc @harryalbert
Requesting review from @rachaelrenk and @hongyi-chen.

Conversation: https://app.warp.dev/conversation/01b5c501-7617-4e46-b77a-092609b28ac5
Run: https://oz.warp.dev/runs/019fae9f-b510-7716-912f-f373be49b552

This PR was generated with Oz.

Auto-drafted from PRODUCT.md for CODE-1827 and behavior verified against
warpdotdev/warp source. Documents the terminal UI /handoff command that forks
a local Warp Agent conversation into an Oz cloud run.

Co-Authored-By: Oz <oz-agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Jul 29, 2026
@vercel

vercel Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 29, 2026 4:13pm

Request Review

@rachaelrenk

Copy link
Copy Markdown
Contributor Author

Tweaking the origin point of this workflow. Too many draft PRs coming through for features that aren't ready or don't necessarily need docs.

@rachaelrenk rachaelrenk closed this Aug 3, 2026
rachaelrenk added a commit that referenced this pull request Aug 25, 2026
… footguns (#619)

* docs: request reviewers for real and lead ambient PRs with a feature summary

Two fixes to the ambient new-feature docs pipeline (GROW-6093).

1. Actually request reviewers. The drafted PR only named reviewers in
   prose, which puts nothing in GitHub's review queue: docs #414, #415,
   #416 and #417 all named reviewers in the body and received zero
   reviews, three with an empty requested-reviewers list. Wire a required
   `gh pr edit --add-reviewer` step into missing_docs drift-watch step 7
   and into the create_pr skill, with the `dannyneira` fallback that
   release-docs-update.yml already uses, plus a verification read-back so
   a silently skipped assignment is caught. The prose /cc mention stays.

   suggest_reviewers.py gains `--reviewers-only` so the step can consume
   the resolved set without scraping the human-readable table.

2. Lead the PR body with a feature summary. Drafting PRs must open with
   `## What this feature does`: plain language, what the feature does for
   the user, ending with the shipped-in version and date read from
   check_new_release.py --json. Budget 75 words. check_pr_body.py gains
   `--require-lead-section`, asserting the heading is present once, is the
   first heading, is non-empty, and is within budget.

Co-Authored-By: Warp <agent@warp.dev>

* docs: address review — per-reviewer requests, first-content check, CI wiring

Blocking fix. The reviewer verification was emptiness-only, so the owning
engineer could be dropped silently — the exact bug this PR exists to fix.
`gh pr edit --add-reviewer a,b,c` is one atomic mutation, so a single
unassignable entry rejected the whole list and the `||` then replaced every
resolved owner with the fallback; a non-empty readback still passed. This is
live: `warpdotdev/oss-maintainers` is the root-rule owner in the warp client
repo and appears in most resolutions, but `/repos/warpdotdev/docs/teams` is
empty, so it cannot be requested here. Now each reviewer is requested in its
own call and the readback is compared against the resolved set, with partial
results reported. Also fixed the readback jq: the old
`[.reviewRequests[].login // .reviewRequests[].name]` silently drops teams
from a mixed list (verified).

Also:
- check_lead_section now asserts the summary is the first *content*, not just
  the first heading. A body opening with unheaded spec/workflow/run-ID
  preamble previously exited 0, which is the shape the check exists to stop.
- _iter_non_code_lines skips HTML comments, so a `##` inside a multi-line
  comment no longer displaces the lead section — same class already handled
  for code fences.
- Wired test_check_new_release.py into CI. The earlier deferral was wrong:
  #586 does not touch ci.yml and this PR already edits it, while
  missing_docs/SKILL.md advertises the test as covered.
- suggest_reviewers.py routes resolution diagnostics to stderr under
  --reviewers-only, so a fallback leaves a trace without polluting stdout.
- Removed the duplicated reviewer snippet from missing_docs; create_pr holds
  the canonical copy. The copies had already diverged, and the missing_docs
  one used `[[ -z ... ]] && ...`, which returns 1 and would abort a `set -e`
  scheduled run.
- Backticked the date in the worked example; marked the drafting-only lines
  in the copy-paste heredoc.
- Tests locking in first-content, HTML-comment banners, multi-line comments,
  CRLF bodies, and the stderr diagnostics.

Co-Authored-By: Warp <agent@warp.dev>

* create_pr: stop the fallback reviewer from masking a dropped owner

Review catch on #619. Step 4 appended FALLBACK_REVIEWER to GOT, but GOT
answers "which resolved owners did I actually request". Counting the
fallback there let the verification pass on a run where every real owner
was rejected -- the exact silent failure the section exists to prevent,
two paragraphs after it says "verify against the resolved set, not
against emptiness".

Traced against the documented snippet with a stubbed gh. Resolved owners
alice and bob, both rejected, fallback accepted:

  before:  warning: requested 1/2 resolved reviewers
           Requested reviewers: dannyneira
           exit 0

  after:   ERROR: none of the 2 resolved owners could be requested
           (wanted: alice bob); only the fallback is assigned.
           exit 1

The fallback now stays out of GOT, and the outcomes are reported as four
distinct states rather than one count: all owners requested, a partial
result naming who is missing, owners resolved but none requested (an
error, because the PR has the wrong reviewer), and nothing resolved at
all (a note, because the fallback is the intended pall there). Not even
the fallback landing remains a hard failure.

Verified by extracting the snippet from SKILL.md and executing it against
a stubbed gh across all five cases, so the documented text is what was
tested rather than a paraphrase of it.

Co-Authored-By: Warp <agent@warp.dev>

* create_pr: trust the read-back, and fix comment/fence ordering

Two review catches on #619.

1. Reviewer verification trusted gh's exit status

The section warns that `gh pr edit` can exit 0 while quietly skipping a
reviewer, then verified against GOT -- which is built from those exit
statuses. A silently skipped owner passed.

Verification now compares the read-back against WANT. Step 4's fallback
also keys off the read-back rather than GOT, because when gh exits 0 for
every owner and requests none of them, a GOT-based check skips the
fallback entirely and leaves the PR with no reviewer at all.

Match on the last path segment, lowercased: a team resolves as org/team
but reads back as its bare slug, so a naive compare reported every team
as missing.

Verified by extracting the snippet from SKILL.md and running it against
a stubbed gh across nine cases, including a stub that exits 0 without
recording the reviewer:

  bob silently skipped -> warning names bob   (previously silent)
  all silently skipped -> fall  all silently skipped -> fall  all silently skipped -> fall es its bare slug, no false "missing"

2. Fence detection ran before comment stripping

A ``` line inside an HTML comment opened a phantom code block that ate
the closing --> and every line after it, including the lead heading. A
valid body failed with "missing required lead section", which reads as
an authoring mistake rather than a parser bug.

Precisely: only an *odd* number of fence lines inside a comment breaks
it. A balanced pair opens and closes a phantom block that happens to end
before the -->, so it passed by luck. The tests say which case is the
real regression rather than implying all of them were.

The fix honors fence state first, then strips comments, then looks for a
fence in the visible text. Simply reordering the two would break the
mirror case:mirror case:mirror case:mirror case:mirror case:mirror case:mirror cs a comment would swallow tmirror case:mirror case:mirror cver both directions plus a guard tmirror case:mirror case:mirror case:mirror case:mirror case:mirror case:mirror cs a comment would swallow tmirror caseent@warp.dev>

* create_pr: verify the fallback reviewer by name, not by read-back emptiness

Review catch on #619 (QUALITY-1875 rework). When owner resolution came back
empty and the PR already carried an unrelated reviewer, the prior "is
$REQUESTED non-empty" check treated that unrelated reviewer as proof the
dannyneira fallback had landed, so it skipped verifying/re-requesting the
fallback by name -- and the script still printed "fallback requested" and
exited 0 even when dannyneira was never assigned.

Added a has_reviewer helper that checks the read-back for a specific
reviewer, used it to gate the fallback request/verification when resolution
was empty, and split the final error check so a fallback that truly can't be
assigned is reported as a failure instead of masked by an unrelated
reviewer already on the PR.

Added test_request_reviewers.py, which extracts the documented snippet from
SKILL.md and runs it against a stubbed gh/suggest_reviewers.py across the
normal-resolution, empty-resolution, and pre-existing-unrelated-reviewer
cases. test_unrelated_reviewer_does_not_mask_fallback_failure fails against
the pre-fix snippet and passes after the fix. Wired into ci.yml.

Co-Authored-By: Warp <agent@warp.dev>

* missing_docs: fix the three sandbox footguns validation run #2 hit

The second drift-watch validation run surfaced three ways the skill misleads an
unattended agent. All three are documentation gaps in the skill, not code bugs.

1. Working directory. Every path in the skill is relative to the docs repo root,
   but the skill never says so, and a sandbox commonly starts one level up. The
   failure mode is the problem: python3 exits 2 with "can't open file", which is
   the same exit code audit_docs.py uses to fail loud on a broken environment.
   An agent that reads the code and not the message concludes a sanity guard
   tripped and stops. State the cwd requirement up front, name the collision,
   and repeat it at the release-gate step and in the scheduled-agent prompt --
   the prompt is the only one of the three a cron run is guaranteed to read.

2. npm install. `npm run build` is the only validation this repo has and it
   needs node_modules, which a fresh sandbox does not have. Add `npm ci` as a
   stated precondition in Requirements and at both build sites.

3. Surface-map key edits. A rename sweep run across feature_surface_map.md
   corrupted an    corrupted an    corrupted an    corrupted an    corrupted an    corrupted aon    corrupted an    corrupted an    corrupted an    corrupted an    corrupted an map entry is a literal code identifier that only matches
   because it matches ex   because it matches ex   because it matches ex   beCo-Authored-By: Warp <agent@warp.dev>

---------

Co-authored-by: Warp <agent@warp.dev>
Co-authored-by: warp-agent-staging[bot] <240773466+warp-agent-staging[bot]@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — c85c03e0 Deployed Jul 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant