Skip to content

fix(openhands-automation): read secrets from the OpenHands API on Cloud and Enterprise runs - #709

Merged
hieptl merged 1 commit into
mainfrom
hieptl/ohe-3347
Sep 30, 2026
Merged

hieptl merged 1 commit into
mainfrom
hieptl/ohe-3347

Conversation

@hieptl

@hieptl hieptl commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

HUMAN:

I verified the changes.


AGENT:

Why

The openhands-automation skill tells custom scripts to read secrets with a get_secret() helper that only uses AGENT_SERVER_URL. The automation service sets that variable in local mode only (backends/local.py). OpenHands Cloud and Enterprise run the service in cloud mode, where a run gets OPENHANDS_CLOUD_API_URL, SANDBOX_ID and SESSION_API_KEY instead (backends/cloud.py, dispatcher.py), and user secrets are served by the app server at /api/v1/sandboxes/{SANDBOX_ID}/settings/secrets/{name} — the same endpoint OpenHandsCloudWorkspace.get_secrets() calls.

On Enterprise, a Slack DM automation built from the skill's own no-LLM example failed with unknown url type: '/api/settings/secrets/SLACK_BOT_TOKEN'. Pointing it at the sandbox's agent server instead returned 404, because nothing fills that store in cloud mode, and the OpenHands agent ended up hardcoding the Slack token into main.py.

Summary

  • All three get_secret() copies (the SKILL.md helper, plus the deterministic-script section and the complete no-LLM example in custom-automation.md) now branch on AGENT_SERVER_URL, the same signal the service's presets use to detect local mode. When it is set, the agent-server lookup is unchanged; otherwise the helper calls the sandbox-scoped endpoint on OPENHANDS_CLOUD_API_URL with the same X-Session-API-Key header. A missing cloud variable raises a KeyError that names it instead of an opaque URL error.
  • The run environment table documents OPENHANDS_CLOUD_API_URL and SANDBOX_ID and marks AGENT_SERVER_URL as local Agent Canvas only; the prose and troubleshooting lines that described the old lookup now match.
  • skills/index.js regenerated with npm run build:skills.

Issue Number

OHE-3347

How to Test

  1. From the repo root, run the stub check below. It extracts every get_secret() copy from the two docs, so it exercises exactly what gets copied into a script, and calls each one against a local stub server in both modes.
    • On main, cloud mode reproduces the reported failure for all three copies: ValueError: unknown url type: '/api/settings/secrets/SLACK_BOT_TOKEN'.
    • On this branch, cloud mode requests /api/v1/sandboxes/<SANDBOX_ID>/settings/secrets/<name> and local mode still requests /api/settings/secrets/<name>, both with the session key header.
  2. uv run --group test pytest tests/ — 1091 passed, 23 skipped.
  3. python scripts/sync_extensions.py --check passes, and npm run build:skills leaves skills/index.js unchanged.
Stub check and output
python3 - <<'EOF'
import os, re, threading
from http.server import BaseHTTPRequestHandler, HTTPServer

class Stub(BaseHTTPRequestHandler):
    def do_GET(self):
        print(f"    GET {self.path}  X-Session-API-Key={self.headers['X-Session-API-Key']}")
        self.send_response(200); self.end_headers(); self.wfile.write(b"xoxb-test\n")
    def log_message(self, *args): pass

srv = HTTPServer(("127.0.0.1", 0), Stub)
threading.Thread(target=srv.serve_forever, daemon=True).start()
base = f"http://127.0.0.1:{srv.server_port}"

docs = "".join(open(p).read() for p in (
    "skills/openhands-automation/SKILL.md",
    "skills/openhands-automation/references/custom-automation.md"))
helpers = re.findall(r"^def get_secret\(.*?^        return r\.read\(\)\.decode\(\)\.strip\(\)$", docs, re.S | re.M)

modes = {
    "cloud": {"OPENHANDS_CLOUD_API_URL": base, "SANDBOX_ID": "sbx-1", "SESSION_API_KEY": "sk-1"},
    "local": {"AGENT_SERVER_URL": base, "SESSION_API_KEY": "sk-1"},
}
for i, src in enumerate(helpers, 1):
    ns = {}
    exec("import os, urllib.request\n" + src, ns)
    for mode, env in modes.items():
        for k in ("AGENT_SERVER_URL", "OPENHANDS_CLOUD_API_URL", "SANDBOX_ID"):
            os.environ.pop(k, None)
        os.environ.update(env)
        print(f"copy {i} {mode}:")
        try:
            print(f"    -> {ns['get_secret']('SLACK_BOT_TOKEN')!r}")
        except Exception as e:
            print(f"    -> {type(e).__name__}: {e}")
EOF

Output on this branch:

copy 1 cloud:
    GET /api/v1/sandboxes/sbx-1/settings/secrets/SLACK_BOT_TOKEN  X-Session-API-Key=sk-1
    -> 'xoxb-test'
copy 1 local:
    GET /api/settings/secrets/SLACK_BOT_TOKEN  X-Session-API-Key=sk-1
    -> 'xoxb-test'
copy 2 cloud:
    GET /api/v1/sandboxes/sbx-1/settings/secrets/SLACK_BOT_TOKEN  X-Session-API-Key=sk-1
    -> 'xoxb-test'
copy 2 local:
    GET /api/settings/secrets/SLACK_BOT_TOKEN  X-Session-API-Key=sk-1
    -> 'xoxb-test'
copy 3 cloud:
    GET /api/v1/sandboxes/sbx-1/settings/secrets/SLACK_BOT_TOKEN  X-Session-API-Key=sk-1
    -> 'xoxb-test'
copy 3 local:
    GET /api/settings/secrets/SLACK_BOT_TOKEN  X-Session-API-Key=sk-1
    -> 'xoxb-test'

Not exercised against a live Cloud or Enterprise sandbox. The endpoint contract comes from the app server's sandbox router (X-Session-API-Key auth, text/plain body), which the SDK's OpenHandsCloudWorkspace.get_secrets() and the prompt/plugin presets already rely on.

Video/Screenshots

N/A — documentation-only change. The stub output above shows the before/after behavior.

Notes

  • Scope is the skill's guidance only. The same local-only lookup is still in:

    • the get_secret() helper of the shipped automation scripts (skills/{slack-channel-monitor,news-digest,github-pr-reviewer,github-issue-to-pr,github-repo-monitor,github-agents-md-maintainer,gitlab-issue-to-mr,jira-issue-to-pr}/scripts/main.py);
    • the SDK templates in custom-automation.md, which pass AGENT_SERVER_URL as cloud_api_url;
    • the relative LookupSecret URLs in references/security.md.

    These need their own changes (several of the scripts have tests), so they are left for follow-ups.

  • docs: turn automation creation into a phased interview, close reliability gaps #455 edits the same two files and keeps the old helper, so it will need a rebase after this lands.

…ud and Enterprise runs

The skill's get_secret() helper only read AGENT_SERVER_URL, which the
automation service injects in local mode only. On OpenHands Cloud and
Enterprise the service runs in cloud mode: it injects
OPENHANDS_CLOUD_API_URL, SANDBOX_ID and SESSION_API_KEY instead, and user
secrets are served by the app server at
/api/v1/sandboxes/{SANDBOX_ID}/settings/secrets/{name}, not by the
sandbox's agent server. Scripts built from the skill therefore failed
with "unknown url type", and pointing them at the agent server's own
/api/settings/secrets returned 404 because nothing populates that store
in cloud mode.

- Branch all three get_secret() copies on AGENT_SERVER_URL, the same
  signal the automation service's presets use: keep the agent-server
  lookup when it is set, otherwise call the sandbox-scoped endpoint on
  OPENHANDS_CLOUD_API_URL with the same X-Session-API-Key header.
- Document OPENHANDS_CLOUD_API_URL and SANDBOX_ID in the run environment
  table, mark AGENT_SERVER_URL as local Agent Canvas only, and update the
  prose and troubleshooting lines that described the old lookup.
- Regenerate skills/index.js.

Refs OHE-3347
@github-actions github-actions Bot added the type: fix A bug fix label Sep 30, 2026
@hieptl hieptl self-assigned this Sep 30, 2026
@hieptl
hieptl marked this pull request as ready for review September 30, 2026 06:18
@hieptl
hieptl merged commit a3135ba into main Sep 30, 2026
17 of 18 checks passed
@hieptl
hieptl deleted the hieptl/ohe-3347 branch September 30, 2026 16:08
@openhands-release-bot openhands-release-bot Bot added the released: v0.26.0 Shipped in v0.26.0 label Oct 2, 2026
@openhands-release-bot

Copy link
Copy Markdown
Contributor

🚀 Released in v0.26.0.

enyst added a commit that referenced this pull request Oct 4, 2026
…on Cloud

The worked example added by this PR carried the pre-#709 get_secret, which
only reads from the agent server. Match the helper now on main so the
example also fetches secrets from the OpenHands API on Cloud and
Enterprise runs. Regenerate skills/index.js.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
enyst added a commit that referenced this pull request Oct 4, 2026
…ic scripts (#686)

* fix(openhands-automation): stop steering agents away from deterministic scripts

The skill's rule 0 tells agents to prefer a no-LLM custom script for
deterministic tasks, but the rest of the text pulled the other way:

- Prompt-preset examples modelled deterministic tasks (hourly health
  check, run tests on push, weekly temp-file cleanup). Replace them with
  tasks that need judgment: error triage, bug reproduction, dependency
  review, flagging risky pushes.
- references/custom-automation.md opened with "Do NOT use this reference
  unless the user has explicitly requested a custom automation", which
  contradicted rule 0 and the Reference Files section. Remove it and list
  deterministic tasks as a first-class reason to use the custom path.
- Add a complete no-LLM worked example (hourly health check with Slack
  alert) next to the prompt-preset examples, so both paths are equally
  concrete.
- Make consent symmetric: present the options side by side and build the
  one the user picks. The "ready to deploy?" confirmation applies to
  every path; custom scripts no longer need an extra permission step.

Also drops a duplicated references/security.md bullet and regenerates
the skills catalog.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(openhands-automation): fix rule cross-reference and rebalance README

Address review feedback on #686:

- SKILL.md: the polling section pointed at rule 5, which became rule 4
  when rules 2-4 were collapsed.
- README.md: stop presenting the prompt preset as the recommended default
  and custom automations as advanced-only; list custom scripts alongside
  prompt-based creation and point to the no-LLM worked example.
- Regenerate skills/index.js.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(openhands-automation): make the no-LLM example's get_secret work on Cloud

The worked example added by this PR carried the pre-#709 get_secret, which
only reads from the agent server. Match the helper now on main so the
example also fetches secrets from the OpenHands API on Cloud and
Enterprise runs. Regenerate skills/index.js.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* chore: remove tracker file committed by mistake

b588ba6 picked up .beads/issues.jsonl through the local bd pre-commit
hook, which stages that file on every commit. It is local task-tracker
data and does not belong in this repository.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released: v0.26.0 Shipped in v0.26.0 type: fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants