Skip to content

feat: add langsmith deployment subcommand (LangGraph CLI port)#32

Open
William FH (hinthornw) wants to merge 6 commits intomainfrom
wfh/lg
Open

feat: add langsmith deployment subcommand (LangGraph CLI port)#32
William FH (hinthornw) wants to merge 6 commits intomainfrom
wfh/lg

Conversation

@hinthornw
Copy link
Copy Markdown

Summary

  • Translates the LangGraph Python CLI into native Go as langsmith deployment <cmd>
  • Adds commands: up, build, dev, deploy (with list, delete, logs), dockerfile, new
  • dev delegates to local langgraph if installed, otherwise uses uvx/uv (cache-first, no forced internet)
  • New internal/deployment package: config validation, Dockerfile generation, Docker Compose YAML, host backend API client, subprocess execution, progress spinner, templates

Commands

Command Description
langsmith deployment up Launch LangGraph API server via Docker Compose
langsmith deployment build --tag img Build Docker image from langgraph.json
langsmith deployment dev Run dev server (delegates to langgraph/uvx)
langsmith deployment deploy Full build+push+deploy to LangSmith
langsmith deployment deploy list List deployments
langsmith deployment deploy delete ID Delete a deployment
langsmith deployment deploy logs Fetch build/deploy logs
langsmith deployment dockerfile PATH Generate Dockerfile
langsmith deployment new PATH --template T Scaffold from template

Test plan

  • 48 unit tests covering config validation, docker compose generation, API client, helpers, command tree, flags, and e2e deploy list
  • All existing tests still pass (go test ./...)
  • Manual testing of langsmith deployment deploy list --api-key ...
  • Manual testing of langsmith deployment dev with local langgraph install
  • Manual testing of langsmith deployment up with Docker

🤖 Generated with Claude Code

Translate the LangGraph Python CLI into native Go as a subcommand of the
langsmith CLI. This adds `langsmith deployment <cmd>` with the following
commands:

- `up` — launch LangGraph API server via Docker Compose (Redis, Postgres, API)
- `build` — build a Docker image from langgraph.json config
- `dev` — run dev server (delegates to local `langgraph` or `uvx`)
- `deploy` — full build+push+deploy flow to LangSmith
- `deploy list` — list existing deployments
- `deploy delete` — delete a deployment
- `deploy logs` — fetch build/deploy logs
- `dockerfile` — generate Dockerfile (and optionally docker-compose.yml)
- `new` — scaffold a new project from a template

Includes internal/deployment package with config validation, Dockerfile
generation, Docker Compose YAML generation, host backend API client,
subprocess execution, progress spinner, and template management.

48 tests covering config validation, docker compose generation, API client,
helper utilities, command tree structure, flag verification, and end-to-end
deploy list integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread internal/deployment/config.go Fixed
Comment thread internal/deployment/templates.go Fixed
devin-ai-integration[bot]

This comment was marked as resolved.

William FH (hinthornw) and others added 4 commits March 13, 2026 13:26
- Check error return values (errcheck)
- Lowercase error strings (staticcheck ST1005)
- Simplify loop to append (gosimple S1011)
- Simplify bool return (gosimple S1008)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Zip Slip: validate extracted file paths stay within destination directory
- Unsafe quoting: properly escape JSON in Dockerfile ENV directives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use filepath.Clean + HasPrefix("..") check directly on the name
before joining, which is the standard pattern CodeQL recognizes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CodeQL tracks taint from f.Name through to filepath.Join result.
Guard the targetPath directly with strings.HasPrefix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

- docker.go: debugger depends_on langgraph-postgres only when using
  local DB (includeDB), not when external postgres-uri is provided
- hostbackend.go: URL-encode query parameters using net/url.Values
  to prevent injection via special characters in user input
- deployment.go: follow mode in logs command tracks last timestamp
  and updates start_time in payload to avoid re-printing old entries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

View 14 additional findings in Devin Review.

Open in Devin Review

if err != nil {
exitErrorf("Creating temp dir: %v", err)
}
defer func() { _ = os.RemoveAll(tmpDir) }()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Temp directory with Docker registry auth token not cleaned up on error due to os.Exit skipping defer

In the deploy command, a temp directory is created at line 556 to store a Docker config containing a base64-encoded OAuth access token for the container registry. The cleanup is registered via defer func() { _ = os.RemoveAll(tmpDir) }() at line 560. However, all error paths after this point call exitErrorf (internal/cmd/root.go:111-113), which calls os.Exit(1). In Go, os.Exit does not run deferred functions, so the temp directory (containing config.json with the registry auth token) persists on disk in /tmp/langsmith-deploy-*/ after any failure in tagging (line 579), pushing (line 584), updating deployment (line 593), or timeout (line 614).

Prompt for agents
In internal/cmd/deployment.go, the defer on line 560 won't run because exitErrorf calls os.Exit(1), which skips deferred functions. To fix this, either:

1. Replace the defer with explicit cleanup calls before each exitErrorf after line 560 (lines 572, 579, 584, 593, 614, 641), e.g., add `_ = os.RemoveAll(tmpDir)` before each exitErrorf call.

2. Or refactor the deploy command's Run function to return errors instead of calling os.Exit directly, and handle cleanup in a wrapper that calls os.RemoveAll(tmpDir) before exiting.

Option 1 is simpler. You would add `_ = os.RemoveAll(tmpDir)` before each exitErrorf call on lines 572, 579, 584, 593, 614, and 641.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

// Update deployment with secrets
secrets := deployment.SecretsFromEnv(cfg)
fmt.Fprintln(os.Stderr, "Updating deployment...")
secretMaps := append([]map[string]string{}, secrets...)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 SecretsFromEnv nil return converted to non-nil empty slice, always sending empty secrets to API

On line 590, append([]map[string]string{}, secrets...) converts a nil return from SecretsFromEnv into a non-nil empty slice []map[string]string{}. This is then passed to UpdateDeployment (internal/deployment/hostbackend.go:154), which uses if secrets != nil (line 160) to decide whether to include "secrets" in the API payload. Because the append wrapper always produces a non-nil slice, the API always receives "secrets": [], even when the user has no env section in their config (the default is map[string]interface{}{} per internal/deployment/config.go:194). This could cause the API to clear any existing deployment secrets that were configured through other means (e.g., the LangSmith UI).

Suggested change
secretMaps := append([]map[string]string{}, secrets...)
_, err = client.UpdateDeployment(deploymentID, imageURI, secrets)
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants