Repository navigation
feat: nan run / nan runs (agent automations phase 1) - #41
Merged
Merged
Conversation
Types mirror the run object, event and list shapes of the Phase 1 runs API. The stream reader frames on LF (tolerating CRLF), reconnects with Last-Event-ID up to five times in a row with backoff, drops replayed events, treats 45 s of silence as a dead connection, and never applies the 30 s request timeout to the stream. Session cookie first, member API key as Bearer when there is no session; 401 is ErrSessionExpired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every string a run produces comes from the member's workspace, so ANSI escape sequences (CSI, OSC, DCS...), C0/C1 controls, carriage returns and bidi overrides are stripped before anything reaches the terminal. JSON output escapes C1 and bidi controls too. ExitCode maps a finished run onto the documented exit codes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nan run creates a run on /v1/runs and streams it, exiting with the run's outcome (0/1/2/3/4), 64 for usage errors, 65 for auth, 69 when the platform or the stream is lost (naming the run), 75 when detached. Ctrl-C detaches and prints how to follow or cancel; a second Ctrl-C within 2 s cancels. --ws defaults to the member's only workspace and lists the names when there are several. On run/runs, flag errors from cobra exit 64; other commands keep exiting 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- stream: the idle limit also covers the wait for response headers; a 200 that is not text/event-stream is a failed connection; the retry budget resets only on a connection that delivered a frame or stayed up heartbeating, so nothing reconnects forever; frames capped at 4 MiB; redirects are never followed (credential stays put) - run: arguments are checked before the session; a run that ends as Ctrl-C lands reports its outcome; repeated state frames print once; a third Ctrl-C after the cancel window is no longer swallowed; the run id the server returns is validated; stream-lost messages are sanitised; stdin on a terminal says it is waiting for the prompt - runs: missing id says so; empty filtered list and --limit 100 hints; logs without -f says when the run is still going - json: DEL escaped too - tests: header timeout, non-SSE 200, heartbeat-only, budget reset, 429 retry, malformed end, oversized line/frame, redirects, logs -f 401/404, runtime errors exit 1, no credential in any output - README: set -e and --detach, idempotency for retrying scripts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- json: invalid UTF-8 carried through json.RawMessage (a lone 0x9b is an 8-bit CSI) goes out as �; DEL escaped - the RunE path is the tested path: doRun moved into the tests, which drive buildRunRequest + startRun exactly as the command does - stream: replayed events and state frames no longer refill the retry budget; an end frame for another run is refused; reconnects are announced on stderr - run: --worktree/--no-worktree conflict reports our own message; --timeout help shows 30m; help warns that argv prompts land in shell history and ps - runs logs -f help says Ctrl-C stops following, the run goes on - tests: logs pagination (advance and stuck cursor), lone C1 byte in --json for logs and run, logs -f exit 4, atomic counters in handlers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Blocking reviews (Claude Code, Opus), three separate lenses, all on head
Reminder: do not merge until /v1/runs is live — merging publishes v0.1.24 via tag.yml. 🤖 Generated with Claude Code |
The idle timer only reset on a complete line, so an oversized line that took longer than IdleTimeout to arrive (1 MiB under -race on a slow Linux runner vs the 200 ms test limit) ended the first connection as an idle reconnect; only the retry hit bufio.ErrTooLong. The oversize error was therefore retried once on CI. The idle clock now tracks the last bytes read from the body, and a read result that is already pending wins over the timer, so an oversized line or frame is always fatal on the first connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
go.mod is the single source for CI and release (go-version-file), so the bump covers vet/test, govulncheck and the release build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merging this PR publishes a release:
tag.ymltags and releases any merge tomainthat changesinternal/tui.Version(bumped here to0.1.24). The/v1/runsAPI this talks to does not exist yet (cloud-api T1–T4 of Agent Automations Phase 1), so this must wait untilhttps://cloud-api.nan.builders/v1/runsis deployed and the feature gate is on. No tags or releases were created.What
NaN Agent Automations Phase 1, task T5 (NaN community tenant). Spec:
helmcode/nan-devopsAGENT-AUTOMATIONS-PHASE1.md§2.1.3 (HTTP API + SSE frames), §2.4 (CLI), §5 "nan-cli (T5)". Issue: helmcode/nan#162.internal/api/runs.go—RunsBaseURL = https://cloud-api.nan.builders(notapi.nan.builders: the edge Worker rewrites bodies, spec §0.3). Types mirror §2.1.3. Session cookienan_sessionfirst; member API key asAuthorization: Bearerwhen there is no session; 401 →ErrSessionExpired. Both error envelopes parsed (OpenAI on/v1, bare on/api).data:),Last-Event-IDreconnect up to 5 failures in a row with exponential backoff (1s→16s; the budget resets after any byte, so the 30-min server close never exhausts it), replayed events de-duplicated by seq, 45 s idle timeout (the server heartbeats every 15 s), no client timeout on the stream (the 30 s timeout stays on every other request). 401/404 are not retried. Lines over 1 MiB are fatal.internal/runs/render.go— event → terminal lines. All guest text is sanitised: CSI/OSC/DCS/SOS/PM/APC and two-byte escapes, C0 (except\n/\t), DEL, C1, CR, bidi overrides; invalid UTF-8 → U+FFFD.--jsonoutput also escapes C1/bidi controls (encoding/json already escapes C0).cmd/run.go,cmd/runs.go,cmd/exit.go— exit-code table of §2.4 (0/1/2/3/4 run outcome; 64 usage; 65 auth; 69 unavailable / stream lost, naming the run; 75 detached). Ctrl-C detaches and printsnan runs logs <id> -f/nan runs cancel <id>; a second Ctrl-C within 2 s cancels. Ctrl-C is only captured once the run is being followed.--wsdefaults to the member's only workspace (GET /api/workspaces), else exit 64 listing the names. Run ids are validated as UUIDs before they go into a path. Client-side checks mirror the server limits (prompt 1..32768 bytes, UTF-8, no NUL; timeout 1m..2h; model and idempotency-key regexes) so mistakes exit 64 without a round trip. Unset optional flags are omitted from the body so the platform defaults apply.cmd/root.go—ExecutehonoursExitError; onrun/runsa cobra refusal (unknown flag, missing id, mutually exclusive flags) exits 64. Other commands keep exiting 1.0.1.23 → 0.1.24(patch).User-facing strings are English, like the rest of the CLI.
Assumptions to confirm against the backend (T1/T4)
messageevents are incremental chunks (coalesced deltas), withfinal: trueon the last; the renderer concatenates them. Iffinalcarries the full text instead, the renderer needs a one-line change.GET /v1/runs?workspace=accepts the workspace name.GET /api/workspacesaccepts the same credentials (session; with API-key-only auth, a refusal tells the member to pass--ws).Tests
Spec-driven,
httptestmocks of §2.1.3 for every item in §5 "nan-cli (T5)":runstreams and exits 0/1/2/3/4 (incl. 4 forconfig_error/agent_not_installed/no_inference_key)Last-Event-ID, duplicate drop, 5-retry give-up (exit 69 naming the run), idle timeout reconnect, no stream timeout, no retry on 401/404, context cancel--wsdefault, ambiguity (exit 64, names sanitised), none, explicit--wsnan auth loginon run/ls/show/cancel; no session → 65; API-key fallbackruns ls/show/logs/cancelJSON output;logs -fexit codes and--afterDeploy notes
None beyond the merge gate above. No env vars, no secrets.
🤖 Generated with Claude Code