From d1679939dbfbdaeb3e00e89d70681626e639a8df Mon Sep 17 00:00:00 2001 From: leechenghsiu Date: Fri, 10 Jul 2026 16:39:24 +0800 Subject: [PATCH 1/4] feat(zeabur-server-ssh): recommend `server exec` over manual ssh2/sshpass Running a command on a dedicated server previously meant `server ssh-info` + a hand-rolled ssh2/sshpass invocation, which corrupts passwords with special characters (the credential is embedded in a multi-layer-escaped shell script) and requires sandbox-specific NODE_PATH. Make `zeabur server exec --id -- ` the primary path: the CLI fetches credentials and runs the command internally, so the password is never shell-quoted and no ssh2/sshpass is needed. Keep the ssh-info + ssh2/sshpass flow as a documented fallback for older CLIs. Regenerated plugins/zeabur/ via scripts/sync-codex-plugin.mjs. DES-803 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../zeabur/skills/zeabur-server-ssh/SKILL.md | 133 +++++++++--------- skills/zeabur-server-ssh/SKILL.md | 133 +++++++++--------- 2 files changed, 136 insertions(+), 130 deletions(-) diff --git a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md index 128ddf5..d2ec4ea 100644 --- a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md +++ b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md @@ -1,53 +1,88 @@ --- name: zeabur-server-ssh -description: Use when debugging services on a user's dedicated server via SSH. Use when needing to inspect pods, check container logs, view k8s resources, or run kubectl commands on the server. Use when "service exec" is insufficient and you need server-level access. Use when user says "check my server", "debug pod", "kubectl", "SSH into server", "check k8s", or "inspect cluster". +description: Use when debugging services on a user's dedicated server via SSH. Use when needing to run a command on the server, inspect pods, check container logs, view k8s resources, or run kubectl commands. Use when "service exec" is insufficient and you need server-level access. Use when user says "check my server", "run X on my server", "debug pod", "kubectl", "SSH into server", "check k8s", or "inspect cluster". --- # Zeabur Server SSH + kubectl > **Always use `npx zeabur@latest` to invoke Zeabur CLI.** Never use `zeabur` directly or any other installation method. -SSH into a user's dedicated server and use kubectl to debug Kubernetes workloads. Zeabur dedicated servers run k3s with kubectl pre-installed. +Run commands on a user's dedicated server and use kubectl to debug Kubernetes workloads. Zeabur dedicated servers run k3s with kubectl pre-installed. -## Step 1: Get SSH Credentials +## Run a command: `server exec` (recommended) + +Run a command on the server in one step. The CLI fetches the credentials and opens +the connection internally, so **you never handle the password** — passwords with +special characters just work, and no `ssh2`/`sshpass` is needed. ```bash -npx zeabur@latest server ssh-info --id -i=false +npx zeabur@latest server exec --id -- ``` -Output is JSON: -```json -{"ip":"1.2.3.4","port":22,"username":"root","password":"xxx"} -``` +Examples: -If you don't know the server ID, list servers first: ```bash -npx zeabur@latest server list -i=false +# Single command +npx zeabur@latest server exec --id -- sudo kubectl get pods -A -o wide + +# Compound command — quote it as ONE argument so && / | stay intact +npx zeabur@latest server exec --id -- 'echo "=== PODS ===" && sudo kubectl get pods -A && echo "=== EVENTS ===" && sudo kubectl get events -A --sort-by=.lastTimestamp | tail -20' ``` -## Step 2: Run Commands via SSH +Notes: -Use the Node.js `ssh2` method by default. Use `sshpass` only when its availability is already known. +- Everything after `--` is the remote command, joined like `ssh host `. + Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. +- stdout/stderr stream live; the remote command's **exit code is propagated**. +- If you don't know the server ID, list servers first: + ```bash + npx zeabur@latest server list -i=false + ``` + (or use the `zeabur-server-list` skill). -### Option A: sshpass (only if already known to be available) +## Common kubectl Commands -```bash -# Single command -sshpass -p '' ssh -o StrictHostKeyChecking=no -p @ sudo kubectl get pods -A +Pass any of these as the command to `server exec`. **Always use `sudo kubectl`** — +the SSH user may not have direct access to the k3s kubeconfig. + +| Task | Command | +|------|---------| +| List all pods | `sudo kubectl get pods -A -o wide` | +| Problem pods only | `sudo kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded` | +| Pod logs | `sudo kubectl logs -n --tail=100` | +| Exec into container | `sudo kubectl exec -n -- ` | +| Node resources | `sudo kubectl top nodes` | +| Pod resources | `sudo kubectl top pods -A --sort-by=memory \| head -20` | +| Describe pod | `sudo kubectl describe pod -n ` | +| Recent events | `sudo kubectl get events -A --sort-by=.lastTimestamp \| tail -30` | +| Restart deployment | `sudo kubectl rollout restart deployment/ -n ` | + +## Fallback: manual SSH (only if `server exec` is unavailable) + +Use this **only** if `server exec` isn't available (e.g. an older CLI). Otherwise +prefer `server exec` above — this path is fragile with special-character passwords. -# Multiple commands in one SSH call -sshpass -p '' ssh -o StrictHostKeyChecking=no -p @ ' - echo "=== PODS ===" && sudo kubectl get pods -A && - echo "=== SERVICES ===" && sudo kubectl get svc -A && - echo "=== EVENTS ===" && sudo kubectl get events -A --sort-by=.lastTimestamp | tail -20 -' +### Step 1: Get SSH credentials + +```bash +npx zeabur@latest server ssh-info --id -i=false ``` -### Option B: Node.js ssh2 (default) +Output is JSON: `{"ip":"1.2.3.4","port":22,"username":"root","password":"xxx"}` + +### Step 2: Connect -The Zeabur agent sandbox has `ssh2` pre-installed. Use this approach by default: +Use the Node.js `ssh2` method by default; use `sshpass` only when its availability +is already known. Do NOT run `which sshpass` to check — it wastes a step where it's +never installed. ```bash +# sshpass (only if already known to be available) +sshpass -p '' ssh -o StrictHostKeyChecking=no -p @ sudo kubectl get pods -A +``` + +```bash +# Node.js ssh2 (the Zeabur agent sandbox has it pre-installed) NODE_PATH=$([ -d /root/.global/node_modules ] && echo /root/.global/node_modules || echo /home/vercel-sandbox/.global/node_modules) node -e " const {Client} = require('ssh2'); const c = new Client(); @@ -68,46 +103,14 @@ c.on('ready', () => { " ``` -For multiple commands, join them with `&&` in the command string: - -```bash -NODE_PATH=$([ -d /root/.global/node_modules ] && echo /root/.global/node_modules || echo /home/vercel-sandbox/.global/node_modules) node -e " -const {Client} = require('ssh2'); -const c = new Client(); -c.on('ready', () => { - c.exec('echo \"=== PODS ===\" && sudo kubectl get pods -A && echo \"=== SERVICES ===\" && sudo kubectl get svc -A && echo \"=== EVENTS ===\" && sudo kubectl get events -A --sort-by=.lastTimestamp | tail -20', (err, stream) => { - if (err) { console.error(err); process.exit(1); } - let out = ''; - stream.on('data', d => out += d); - stream.stderr.on('data', d => out += d); - stream.on('close', () => { console.log(out); c.end(); }); - }); -}).connect({host:'', port:, username:'', password:''}); -" -``` - -## Common kubectl Commands - -**Always use `sudo kubectl`** — the SSH user may not have direct access to the k3s kubeconfig. - -| Task | Command | -|------|---------| -| List all pods | `sudo kubectl get pods -A -o wide` | -| Problem pods only | `sudo kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded` | -| Pod logs | `sudo kubectl logs -n --tail=100` | -| Exec into container | `sudo kubectl exec -n -- ` | -| Node resources | `sudo kubectl top nodes` | -| Pod resources | `sudo kubectl top pods -A --sort-by=memory \| head -20` | -| Describe pod | `sudo kubectl describe pod -n ` | -| Recent events | `sudo kubectl get events -A --sort-by=.lastTimestamp \| tail -30` | -| Restart deployment | `sudo kubectl rollout restart deployment/ -n ` | - ## Tips -- **Use Node.js ssh2 by default (Option B)**: Only use `sshpass` (Option A) if you already know it's available. Do NOT run `which sshpass` to check — it wastes a step in environments where it's never installed. -- **Combine commands**: Batch related checks with `&&` in a single SSH call to reduce round trips. -- **Do NOT use `bash -c '...'` over SSH**: Pass commands directly in SSH quotes. Using `bash -c` causes quoting conflicts. -- **Use `-o wide`**: Adds node name and IP to pod listings, useful for debugging scheduling issues. -- **Namespace matters**: Zeabur services typically run in non-default namespaces. Use `-A` (all namespaces) first to locate the right namespace, then scope subsequent commands with `-n `. -- **Read project docs first**: If a fix attempt fails, exec into the container and check README or config files before blindly checking metrics: `sudo kubectl exec -n -- cat /app/README.md` -- To find server IDs, use the `zeabur-server-list` skill. For simpler container commands that don't need server-level access, use the `zeabur-service-exec` skill instead. +- **Combine commands**: batch related checks with `&&` in a single `server exec` + call to reduce round trips. +- **Use `-o wide`**: adds node name and IP to pod listings, useful for scheduling issues. +- **Namespace matters**: Zeabur services usually run in non-default namespaces. Use + `-A` (all namespaces) first to locate the right one, then scope with `-n `. +- **Read project docs first**: if a fix attempt fails, exec into the container and + check README/config before blindly checking metrics: `sudo kubectl exec -n -- cat /app/README.md` +- To find server IDs, use the `zeabur-server-list` skill. For simpler container + commands that don't need server-level access, use the `zeabur-service-exec` skill. diff --git a/skills/zeabur-server-ssh/SKILL.md b/skills/zeabur-server-ssh/SKILL.md index 128ddf5..d2ec4ea 100644 --- a/skills/zeabur-server-ssh/SKILL.md +++ b/skills/zeabur-server-ssh/SKILL.md @@ -1,53 +1,88 @@ --- name: zeabur-server-ssh -description: Use when debugging services on a user's dedicated server via SSH. Use when needing to inspect pods, check container logs, view k8s resources, or run kubectl commands on the server. Use when "service exec" is insufficient and you need server-level access. Use when user says "check my server", "debug pod", "kubectl", "SSH into server", "check k8s", or "inspect cluster". +description: Use when debugging services on a user's dedicated server via SSH. Use when needing to run a command on the server, inspect pods, check container logs, view k8s resources, or run kubectl commands. Use when "service exec" is insufficient and you need server-level access. Use when user says "check my server", "run X on my server", "debug pod", "kubectl", "SSH into server", "check k8s", or "inspect cluster". --- # Zeabur Server SSH + kubectl > **Always use `npx zeabur@latest` to invoke Zeabur CLI.** Never use `zeabur` directly or any other installation method. -SSH into a user's dedicated server and use kubectl to debug Kubernetes workloads. Zeabur dedicated servers run k3s with kubectl pre-installed. +Run commands on a user's dedicated server and use kubectl to debug Kubernetes workloads. Zeabur dedicated servers run k3s with kubectl pre-installed. -## Step 1: Get SSH Credentials +## Run a command: `server exec` (recommended) + +Run a command on the server in one step. The CLI fetches the credentials and opens +the connection internally, so **you never handle the password** — passwords with +special characters just work, and no `ssh2`/`sshpass` is needed. ```bash -npx zeabur@latest server ssh-info --id -i=false +npx zeabur@latest server exec --id -- ``` -Output is JSON: -```json -{"ip":"1.2.3.4","port":22,"username":"root","password":"xxx"} -``` +Examples: -If you don't know the server ID, list servers first: ```bash -npx zeabur@latest server list -i=false +# Single command +npx zeabur@latest server exec --id -- sudo kubectl get pods -A -o wide + +# Compound command — quote it as ONE argument so && / | stay intact +npx zeabur@latest server exec --id -- 'echo "=== PODS ===" && sudo kubectl get pods -A && echo "=== EVENTS ===" && sudo kubectl get events -A --sort-by=.lastTimestamp | tail -20' ``` -## Step 2: Run Commands via SSH +Notes: -Use the Node.js `ssh2` method by default. Use `sshpass` only when its availability is already known. +- Everything after `--` is the remote command, joined like `ssh host `. + Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. +- stdout/stderr stream live; the remote command's **exit code is propagated**. +- If you don't know the server ID, list servers first: + ```bash + npx zeabur@latest server list -i=false + ``` + (or use the `zeabur-server-list` skill). -### Option A: sshpass (only if already known to be available) +## Common kubectl Commands -```bash -# Single command -sshpass -p '' ssh -o StrictHostKeyChecking=no -p @ sudo kubectl get pods -A +Pass any of these as the command to `server exec`. **Always use `sudo kubectl`** — +the SSH user may not have direct access to the k3s kubeconfig. + +| Task | Command | +|------|---------| +| List all pods | `sudo kubectl get pods -A -o wide` | +| Problem pods only | `sudo kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded` | +| Pod logs | `sudo kubectl logs -n --tail=100` | +| Exec into container | `sudo kubectl exec -n -- ` | +| Node resources | `sudo kubectl top nodes` | +| Pod resources | `sudo kubectl top pods -A --sort-by=memory \| head -20` | +| Describe pod | `sudo kubectl describe pod -n ` | +| Recent events | `sudo kubectl get events -A --sort-by=.lastTimestamp \| tail -30` | +| Restart deployment | `sudo kubectl rollout restart deployment/ -n ` | + +## Fallback: manual SSH (only if `server exec` is unavailable) + +Use this **only** if `server exec` isn't available (e.g. an older CLI). Otherwise +prefer `server exec` above — this path is fragile with special-character passwords. -# Multiple commands in one SSH call -sshpass -p '' ssh -o StrictHostKeyChecking=no -p @ ' - echo "=== PODS ===" && sudo kubectl get pods -A && - echo "=== SERVICES ===" && sudo kubectl get svc -A && - echo "=== EVENTS ===" && sudo kubectl get events -A --sort-by=.lastTimestamp | tail -20 -' +### Step 1: Get SSH credentials + +```bash +npx zeabur@latest server ssh-info --id -i=false ``` -### Option B: Node.js ssh2 (default) +Output is JSON: `{"ip":"1.2.3.4","port":22,"username":"root","password":"xxx"}` + +### Step 2: Connect -The Zeabur agent sandbox has `ssh2` pre-installed. Use this approach by default: +Use the Node.js `ssh2` method by default; use `sshpass` only when its availability +is already known. Do NOT run `which sshpass` to check — it wastes a step where it's +never installed. ```bash +# sshpass (only if already known to be available) +sshpass -p '' ssh -o StrictHostKeyChecking=no -p @ sudo kubectl get pods -A +``` + +```bash +# Node.js ssh2 (the Zeabur agent sandbox has it pre-installed) NODE_PATH=$([ -d /root/.global/node_modules ] && echo /root/.global/node_modules || echo /home/vercel-sandbox/.global/node_modules) node -e " const {Client} = require('ssh2'); const c = new Client(); @@ -68,46 +103,14 @@ c.on('ready', () => { " ``` -For multiple commands, join them with `&&` in the command string: - -```bash -NODE_PATH=$([ -d /root/.global/node_modules ] && echo /root/.global/node_modules || echo /home/vercel-sandbox/.global/node_modules) node -e " -const {Client} = require('ssh2'); -const c = new Client(); -c.on('ready', () => { - c.exec('echo \"=== PODS ===\" && sudo kubectl get pods -A && echo \"=== SERVICES ===\" && sudo kubectl get svc -A && echo \"=== EVENTS ===\" && sudo kubectl get events -A --sort-by=.lastTimestamp | tail -20', (err, stream) => { - if (err) { console.error(err); process.exit(1); } - let out = ''; - stream.on('data', d => out += d); - stream.stderr.on('data', d => out += d); - stream.on('close', () => { console.log(out); c.end(); }); - }); -}).connect({host:'', port:, username:'', password:''}); -" -``` - -## Common kubectl Commands - -**Always use `sudo kubectl`** — the SSH user may not have direct access to the k3s kubeconfig. - -| Task | Command | -|------|---------| -| List all pods | `sudo kubectl get pods -A -o wide` | -| Problem pods only | `sudo kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded` | -| Pod logs | `sudo kubectl logs -n --tail=100` | -| Exec into container | `sudo kubectl exec -n -- ` | -| Node resources | `sudo kubectl top nodes` | -| Pod resources | `sudo kubectl top pods -A --sort-by=memory \| head -20` | -| Describe pod | `sudo kubectl describe pod -n ` | -| Recent events | `sudo kubectl get events -A --sort-by=.lastTimestamp \| tail -30` | -| Restart deployment | `sudo kubectl rollout restart deployment/ -n ` | - ## Tips -- **Use Node.js ssh2 by default (Option B)**: Only use `sshpass` (Option A) if you already know it's available. Do NOT run `which sshpass` to check — it wastes a step in environments where it's never installed. -- **Combine commands**: Batch related checks with `&&` in a single SSH call to reduce round trips. -- **Do NOT use `bash -c '...'` over SSH**: Pass commands directly in SSH quotes. Using `bash -c` causes quoting conflicts. -- **Use `-o wide`**: Adds node name and IP to pod listings, useful for debugging scheduling issues. -- **Namespace matters**: Zeabur services typically run in non-default namespaces. Use `-A` (all namespaces) first to locate the right namespace, then scope subsequent commands with `-n `. -- **Read project docs first**: If a fix attempt fails, exec into the container and check README or config files before blindly checking metrics: `sudo kubectl exec -n -- cat /app/README.md` -- To find server IDs, use the `zeabur-server-list` skill. For simpler container commands that don't need server-level access, use the `zeabur-service-exec` skill instead. +- **Combine commands**: batch related checks with `&&` in a single `server exec` + call to reduce round trips. +- **Use `-o wide`**: adds node name and IP to pod listings, useful for scheduling issues. +- **Namespace matters**: Zeabur services usually run in non-default namespaces. Use + `-A` (all namespaces) first to locate the right one, then scope with `-n `. +- **Read project docs first**: if a fix attempt fails, exec into the container and + check README/config before blindly checking metrics: `sudo kubectl exec -n -- cat /app/README.md` +- To find server IDs, use the `zeabur-server-list` skill. For simpler container + commands that don't need server-level access, use the `zeabur-service-exec` skill. From c6a550e3d96213b94b798cb74a7a42facd6ef6a5 Mon Sep 17 00:00:00 2001 From: leechenghsiu Date: Fri, 10 Jul 2026 23:24:02 +0800 Subject: [PATCH 2/4] docs(zeabur-server-ssh): clarify pipe quoting + pipeline exit codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit review on #72: - Note that piped commands report only the last stage's exit code (`... | tail` hides an upstream failure) — use `set -o pipefail` when that matters. - Warn that any command with `|` / `&&` must be quoted as a single argument to `server exec`, or the local shell splits it and runs part locally. Regenerated plugins/zeabur/ via scripts/sync-codex-plugin.mjs. DES-803 --- plugins/zeabur/skills/zeabur-server-ssh/SKILL.md | 8 +++++++- skills/zeabur-server-ssh/SKILL.md | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md index d2ec4ea..24c7b14 100644 --- a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md +++ b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md @@ -34,6 +34,9 @@ Notes: - Everything after `--` is the remote command, joined like `ssh host `. Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. - stdout/stderr stream live; the remote command's **exit code is propagated**. + A pipeline reports only its **last** command's status, so `... | tail` hides an + upstream failure — prefix `set -o pipefail && ...` inside the quoted command + when an earlier failure must surface. - If you don't know the server ID, list servers first: ```bash npx zeabur@latest server list -i=false @@ -43,7 +46,10 @@ Notes: ## Common kubectl Commands Pass any of these as the command to `server exec`. **Always use `sudo kubectl`** — -the SSH user may not have direct access to the k3s kubeconfig. +the SSH user may not have direct access to the k3s kubeconfig. Any command with a +pipe (`|`) or `&&` must be **quoted as a single argument**, or the local shell +splits it and runs part locally — e.g. +`server exec --id -- 'sudo kubectl top pods -A --sort-by=memory | head -20'`. | Task | Command | |------|---------| diff --git a/skills/zeabur-server-ssh/SKILL.md b/skills/zeabur-server-ssh/SKILL.md index d2ec4ea..24c7b14 100644 --- a/skills/zeabur-server-ssh/SKILL.md +++ b/skills/zeabur-server-ssh/SKILL.md @@ -34,6 +34,9 @@ Notes: - Everything after `--` is the remote command, joined like `ssh host `. Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. - stdout/stderr stream live; the remote command's **exit code is propagated**. + A pipeline reports only its **last** command's status, so `... | tail` hides an + upstream failure — prefix `set -o pipefail && ...` inside the quoted command + when an earlier failure must surface. - If you don't know the server ID, list servers first: ```bash npx zeabur@latest server list -i=false @@ -43,7 +46,10 @@ Notes: ## Common kubectl Commands Pass any of these as the command to `server exec`. **Always use `sudo kubectl`** — -the SSH user may not have direct access to the k3s kubeconfig. +the SSH user may not have direct access to the k3s kubeconfig. Any command with a +pipe (`|`) or `&&` must be **quoted as a single argument**, or the local shell +splits it and runs part locally — e.g. +`server exec --id -- 'sudo kubectl top pods -A --sort-by=memory | head -20'`. | Task | Command | |------|---------| From 3b2753721356c1731f187f284e7fb13615ecea45 Mon Sep 17 00:00:00 2001 From: leechenghsiu Date: Fri, 10 Jul 2026 23:33:51 +0800 Subject: [PATCH 3/4] docs(zeabur-server-ssh): make pipefail example bash-specific set -o pipefail is a bashism and fails under /bin/sh, so wrap it as bash -lc 'set -o pipefail && ...' and note the Bash requirement. CodeRabbit review on #72. DES-803 --- plugins/zeabur/skills/zeabur-server-ssh/SKILL.md | 4 ++-- skills/zeabur-server-ssh/SKILL.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md index 24c7b14..4c985af 100644 --- a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md +++ b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md @@ -35,8 +35,8 @@ Notes: Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. - stdout/stderr stream live; the remote command's **exit code is propagated**. A pipeline reports only its **last** command's status, so `... | tail` hides an - upstream failure — prefix `set -o pipefail && ...` inside the quoted command - when an earlier failure must surface. + upstream failure — wrap it as `bash -lc 'set -o pipefail && ...'` when an + earlier failure must surface (`set -o pipefail` needs Bash, not `/bin/sh`). - If you don't know the server ID, list servers first: ```bash npx zeabur@latest server list -i=false diff --git a/skills/zeabur-server-ssh/SKILL.md b/skills/zeabur-server-ssh/SKILL.md index 24c7b14..4c985af 100644 --- a/skills/zeabur-server-ssh/SKILL.md +++ b/skills/zeabur-server-ssh/SKILL.md @@ -35,8 +35,8 @@ Notes: Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. - stdout/stderr stream live; the remote command's **exit code is propagated**. A pipeline reports only its **last** command's status, so `... | tail` hides an - upstream failure — prefix `set -o pipefail && ...` inside the quoted command - when an earlier failure must surface. + upstream failure — wrap it as `bash -lc 'set -o pipefail && ...'` when an + earlier failure must surface (`set -o pipefail` needs Bash, not `/bin/sh`). - If you don't know the server ID, list servers first: ```bash npx zeabur@latest server list -i=false From b26a6e0fd6b021af204f2fe405824a51a0785229 Mon Sep 17 00:00:00 2001 From: leechenghsiu Date: Sat, 11 Jul 2026 00:38:53 +0800 Subject: [PATCH 4/4] docs(zeabur-server-ssh): simplify pipeline exit-code note Drop the set -o pipefail example (kept drawing portability/quoting nits) and keep just the core caveat: a pipeline reports only its last stage's status. DES-803 --- plugins/zeabur/skills/zeabur-server-ssh/SKILL.md | 7 +++---- skills/zeabur-server-ssh/SKILL.md | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md index 4c985af..3d5be9f 100644 --- a/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md +++ b/plugins/zeabur/skills/zeabur-server-ssh/SKILL.md @@ -33,10 +33,9 @@ Notes: - Everything after `--` is the remote command, joined like `ssh host `. Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. -- stdout/stderr stream live; the remote command's **exit code is propagated**. - A pipeline reports only its **last** command's status, so `... | tail` hides an - upstream failure — wrap it as `bash -lc 'set -o pipefail && ...'` when an - earlier failure must surface (`set -o pipefail` needs Bash, not `/bin/sh`). +- stdout/stderr stream live; the remote command's **exit code is propagated** + (a pipeline reports only its **last** stage's status — `… | tail` hides an + upstream failure, so don't rely on the exit code across a pipe). - If you don't know the server ID, list servers first: ```bash npx zeabur@latest server list -i=false diff --git a/skills/zeabur-server-ssh/SKILL.md b/skills/zeabur-server-ssh/SKILL.md index 4c985af..3d5be9f 100644 --- a/skills/zeabur-server-ssh/SKILL.md +++ b/skills/zeabur-server-ssh/SKILL.md @@ -33,10 +33,9 @@ Notes: - Everything after `--` is the remote command, joined like `ssh host `. Quote a compound command (with `&&` / `|` / redirects) as a **single argument**. -- stdout/stderr stream live; the remote command's **exit code is propagated**. - A pipeline reports only its **last** command's status, so `... | tail` hides an - upstream failure — wrap it as `bash -lc 'set -o pipefail && ...'` when an - earlier failure must surface (`set -o pipefail` needs Bash, not `/bin/sh`). +- stdout/stderr stream live; the remote command's **exit code is propagated** + (a pipeline reports only its **last** stage's status — `… | tail` hides an + upstream failure, so don't rely on the exit code across a pipe). - If you don't know the server ID, list servers first: ```bash npx zeabur@latest server list -i=false