Skip to content

tether skill: Windows breakage + onboarding friction (findings, live-use bugs, and a proposed tether.sh setup) #367

Description

@cyj-git-0825

Consolidates and supersedes #362. Bundled into one issue (bug + UX findings + a proposed fix) for easier triage — happy to split or send a PR if you prefer.

Summary

Tested the tether skill hands-on end-to-end (Windows/Git Bash, hosted api.e2a.dev, plugin e2a@e2a 0.4.5), from a true zero state through a live tethered session. One hard blocker on Windows, one hosted deploy-lag, and several onboarding-UX rough edges. I prototyped a fix + a one-command tether.sh setup; details and a reference implementation below.

Environment: Windows 11, Git Bash (GNU bash 5.2.37, MSYS), python 3.13.5, curl 8.11.0.


1. Blocker — hardcoded python3 breaks tether on Windows

Every script hardcodes python3. On a default Windows install that's the Microsoft Store App Installer redirector shim: it's on PATH (so command -v python3 succeeds) but exits non-zero without running; the real interpreter is python.

$ python3 -c 'print(1)'   # exit 49, no output  (the shim)
$ python  -c 'print(1)'   # 1                    (Python 3.13.5)

Because the scripts use set -uo pipefail without -e, each failed python3 call returns empty and execution limps forward. Result: a configured start fails silently with a misleading message:

$ tether.sh start me@example.com --for 2h
tether: intro send failed (check creds / base url / agent protection)

None of creds/URL/protection is the cause — t_duration_to_expiry, t_state_set (no state file written), and the send payload builder all no-op'd first. Even with a valid key, start can't work: the state file update/poll/listen rely on is never created.

_selftest gives a false green — it prints # syntax OK on this broken machine because it only runs the unconfigured status path (no python3) + bash -n; it never does a real interpreter round-trip.

Suggested fix (resolve once, verify it actually executes):

t_resolve_python() {
  local c
  for c in "${E2A_PYTHON:-}" python3 python; do
    [ -n "$c" ] || continue
    if command -v "$c" >/dev/null 2>&1 \
       && "$c" -c 'import sys; sys.exit(0 if sys.version_info[0]==3 else 1)' >/dev/null 2>&1; then
      command -v "$c"; return 0
    fi
  done; return 1
}
PY="$(t_resolve_python)" || echo "tether: no working Python 3 (set E2A_PYTHON)" >&2
# route the ~20 python3 call sites through "$PY"

Plus: make _selftest do a real interpreter round-trip so it can go red; distinguish "local tooling failed" from "server rejected" in the start error; document the Python-3 prereq in SKILL.md.


2. Onboarding friction (a "just tether" user)

Tether is a bash/curl sidecar, but a new user is routed through two credential worlds and three surfaces:

Before (works, but ~8 manual steps / 3 surfaces):

e2a login → install plugin + mcp add/OAuth → create agent →
dashboard: mint an agent-scoped key by hand → turn HITL off →
hand-edit env (full agent address; local-part alone 404s) → /tether

Pain points: the plugin quickstart is "no key to paste," but tether needs a hand-minted key; mcp add writes nothing tether reads and there's no key-minting MCP tool; the env template's E2A_AGENT_EMAIL needs the full address; status reports config: OK even on a placeholder key (non-empty ≠ valid); chmod 600 in the setup steps is a no-op on NTFS.

Correction to a common assumption: minting an agent-scoped key is not dashboard-only — POST /v1/account/api-keys with scope=agent works with an account key (verified live). So the flow can be automated.


3. Proposed: tether.sh setup (one command)

After:

e2a login → /tether → setup → give email + duration → start + listen → steer from inbox

setup resolves an account credential (~/.e2a/config.json from e2a login, or an e2a_acct_ key), then:

  1. ensures an agent inbox (reuse, else POST /v1/agents a tether-<rand>@<shared_domain>),
  2. forces HITL off (GET+merge+PUT /protection),
  3. mints a least-privilege e2a_agt_ key bound to that inbox (POST /v1/account/api-keys) and stores that (not the account key) in ~/.e2a-tether.env.

Verified from a true zero state (0 agents, 0 keys) on Windows against hosted: setup created the inbox, minted a scoped key (whoamiscope=agent), wrote the env, and start delivered to a real Gmail. I have this working on a branch and am happy to open a PR — or you may prefer to fold it into the "smoother CLI path" the skill already mentions.


4. Live-use findings (from an actual tethered session)

  • Consecutive updates fail (High). update = "reply to the last message"; after one update the last message is the agent's own outbound, so a second update back-to-back → 404 not_found. This is the reply-to-own-outbound behavior; feat: reply to and forward the agent's own outbound messages #360 fixes it but isn't deployed to api.e2a.dev yet (hosted still 404s POST …/messages/{id}/reply on an outbound target even though GET returns it). Net effect until deploy: a tethered agent can post at most one update per user reply — the normal "give a couple status updates while working" pattern is broken.
  • Window expiry cuts off an active conversation (Medium). --for hit TETHER_EXPIRED and auto-stopped mid-decision, with no auto-extend and no warning. Suggest extend-on-activity + an "expiring soon" email.
  • Stopped/expired tether silently drops inbound (Medium). Once unarmed, nothing polls and the user's emails get no reaction/bounce. Correct by design, but surprising — worth a clearer "no longer listening" signal.

What works well: the background listen → wake-the-agent loop reacted to each email within ~20s with zero polling tokens, and the safe-vs-destructive judgment (executed a "make a folder" request immediately; refused a vague "delete temp files" and investigated read-only instead) is exactly right for an AFK channel.


Asks / follow-ups

  1. Merge the interpreter fix (I can PR).
  2. Deploy feat: reply to and forward the agent's own outbound messages #360 so update works (and/or have update fall back to a fresh send in the same conversation_id when the last message is outbound).
  3. Consider tether.sh setup (or your own CLI path) to collapse onboarding to one command.
  4. Window UX: extend-on-activity + expiry warning.
  5. Bigger picture: unify the sign-in so one OAuth powers both the MCP tools and tether (today they're separate credential worlds).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions