Skip to content

openhands changelog

0.10.0

Released 2026-10-26.

Changes

The whole home directory persists

/home/openhands now lives on the data volume (subdirectory home), so CLI logins, MCP OAuth tokens, tool configuration in ~/.config and ~/.local and caches survive a restart. ~/.openhands and ~/workspace stay in their own subdirectories, mounted inside the home, so existing settings, conversations and work trees need no migration. ~/.claude is now part of the home subdirectory instead of a subdirectory claude of its own. A new init container, seed-home, copies the files the image ships in its home (.bashrc, .profile, .bash_logout) into the volume when they are missing and never overwrites one. Caches such as ~/.cache/uv grow on the volume.

MCP servers that each user logs in to with OAuth

A RemoteMCPServer annotated openhands.dev/auth: oauth2 becomes an MCP server that each user authorizes in the browser, for example a vendor's hosted MCP server for mail or calendars. The entry uses the server's own URL and the pre-registered OAuth client in the Secret named by openhands.dev/oauth-client (keys client_id and client_secret), with the scopes of openhands.dev/oauth-scopes. Without openhands.dev/oauth-client the agent registers itself at the server (dynamic client registration). It appears switched off under Settings > MCP: open it, authorize it, switch it on. The agent keeps the tokens encrypted in its settings and refreshes them; a later change of the client id, the scopes or the Secret is written to the entry without logging the user out.

Add-on MCP servers by bundle or name

A RemoteMCPServer labelled mcp.openshift.eu/addon: "true" reaches the agent only when its mcp.openshift.eu/bundle is in mcp.bundles or its mcp.openshift.eu/server is in mcp.servers, for example mcp.bundles: [google-workspace]. The mcp-* charts of this repository stamp these labels. Servers without the add-on label are discovered as before. mcp.labelDomain changes the prefix.

Google issues a refresh token

An authorization request to accounts.google.com asks for offline access, so a Google login keeps working after the first hour. This needs build.enabled.

mcpDiscovery is now mcp

The values under mcpDiscovery moved to mcp. mcpDiscovery is still read in this version and its keys override mcp.

Upgrade

Move any values under mcpDiscovery to mcp; the next minor version stops reading mcpDiscovery. List the add-on bundles or servers an agent should get in mcp.bundles or mcp.servers. The upgrade rebuilds the image and restarts the pod once. What the tools kept in the home outside ~/.openhands and ~/workspace before the upgrade (CLI logins, ~/.config, caches) was not on the volume and starts empty once; log in to the CLIs again.

0.9.6

Released 2026-10-26.

Changes

Google Workspace, Microsoft 365 and Azure CLIs in the image

The image the chart builds now carries gws (Google Workspace CLI 0.22.5), m365 (CLI for Microsoft 365 11.11.0) and az (Azure CLI 2.91.0), next to aws (AWS CLI v2, a Debian package) that it already had. They are entries of build.tools at pinned releases, and the build runs each of them once and fails when one does not start.

build.tools takes two new kinds for them: venv installs a requirement with pip into its own environment under /opt/venv, and tarball installs a binary from a release archive after checking the archive against its SHA-256 digest.

The image holds no credential for these CLIs. Log in from the agent's terminal: gws auth login, m365 setup once and then m365 login, aws configure sso, az login --use-device-code. The login is kept in the container, outside the data volume: after a pod restart, log in again.

The image grows by about 700 MB, mostly the Azure CLI and the CLI for Microsoft 365.

Upgrade

Nothing to do. If you set build.tools yourself, add the three entries from the chart's values.yaml to get the new CLIs. The upgrade starts a new image build, and the pod restarts on the new image when it is done.

0.9.5

Released 2026-10-26.

Changes

Claude Code conversations survive a restart

Claude Code keeps its session store in ~/.claude, which was not on the data volume. After every pod restart it could not resume an open conversation and started a fresh session with only a replay of the history. The chart now mounts ~/.claude from the data volume (subdirectory claude), next to settings and ~/workspace. Sessions started before the upgrade are not recovered; new ones persist. The upgrade restarts the pod once.

0.9.4

Released 2026-10-25.

Changes

MCP servers of every Project the agent may see

MCP discovery lists the OpenShift Projects the agent's ServiceAccount may see and the RemoteMCPServers in each, besides its own namespace. A namespace that refuses the list is skipped, so Kubernetes RBAC on the namespace decides which servers an agent sees: a platform that grants an agent read access to RemoteMCPServers and get on the namespace in another namespace makes that namespace's servers appear. A name that also exists in the own namespace becomes -. Without such grants only the own namespace is discovered, as before.

0.9.3

Released 2026-10-25.

Changes

Room for an agent at work

The agent-canvas container now requests 250m CPU and 2Gi and is limited to 4 CPU and 16Gi, so a busy agent is neither killed for memory nor throttled hard. The sizes follow measured usage of the agents on the platform with generous headroom. The proxy sidecars may use 256Mi instead of 128Mi.

0.9.2

Released 2026-10-25.

Changes

Token proxy: agent identity through the tenant realm

The token proxy copy follows images/agent-identity/token-proxy.py. With AGENT_TOKEN_VIA_URL set, the agent first gets a token from its tenant's own realm with its ServiceAccount token, and that token is the client assertion for the shared realm. Without the variable nothing changes.

0.9.1

Released 2026-10-25.

Fixes

Claude Code ends its session after the first turn

Through a gateway that streams no token usage (an OpenAI-compatible backend behind an Anthropic-format endpoint), the Claude ACP adapter failed with "Cannot read properties of undefined (reading 'input_tokens')" and every later message answered "The Claude Agent session has ended". The image build patches the adapter so missing usage counts as zero.

Upgrade

The image is rebuilt on upgrade.

0.9.0

Released 2026-10-25.

Changes

Every gateway model under Settings > LLM

llm.gatewayModels (default true) adds a profile for every model the gateway lists at /v1/models, on every pod start. Profiles that exist, also those a user edited, are left as they are; the active profile does not change. Before, only llm.model and llm.models were offered. Set it to false to keep that list.

0.8.0

Released 2026-10-25.

Changes

Apps of a team

team and tenant put every object of the chart into <tenant>-<team>, the namespace of the tenant's team, so an app ordered for a team runs next to the team's agents and MCP servers. Without team the chart renders into the release namespace as before.

0.7.0

Released 2026-10-25.

Changes

The agent's own token for MCP servers

The token proxy can carry the agent's own OpenID Connect token on MCP calls. With tokenProxy.agentToken.tokenUrl set, calls under tokenProxy.agentToken.paths (default /mcp) get a token from a client_credentials grant at that endpoint, authenticated with a short-lived ServiceAccount token for tokenProxy.agentToken.audience (federated client authentication). No secret is stored; the token is kept until shortly before it expires. Model calls keep the ServiceAccount token. serviceAccount.labels puts labels on the ServiceAccount, for example the one a platform uses to register it as a client of the realm.

Upgrade

Nothing changes until tokenProxy.agentToken.tokenUrl is set. The pod restarts once, because the token proxy script changed.

0.6.2

Released 2026-10-25.

Changes

Documents and diagrams

The image gains a toolset for architecture work: LibreOffice without GUI (soffice: Word, PowerPoint, Excel, Visio import, PDF export), pandoc, graphviz, plantuml with nwdiag network diagrams, rsvg-convert, ImageMagick and mermaid-cli (mmdc, on the image's Chromium), plus markitdown, python-docx, docxtpl, python-pptx, openpyxl, vsdx and diagrams from PyPI. The build renders a Word file to PDF and a Mermaid, an nwdiag and a diagrams picture to prove they work.

soffice is a wrapper that puts LibreOffice's program directory first in LD_LIBRARY_PATH: the base image's LD_LIBRARY_PATH otherwise resolves the UNO libraries outside their $ORIGIN runpath and LibreOffice does not start.

Skill marketplaces

skills.marketplaces registers skill marketplaces in Claude Code or OpenHands plugin format, for example github:/.

No leftover profile default

The seed moves agent profiles that still use the LLM profile default to the active profile before it removes default; 0.6.0 and 0.6.1 left it in place because the delete was refused while the agent profile default used it.

Wispr Flow icon

The wispr-flow MCP server shows its logo.

Upgrade

A new image builds on the next sync. The image is larger by about 600 MB (LibreOffice and fonts).

0.6.1

Released 2026-10-25.

Changes

Icons on installed MCP servers

Agent Canvas 1.33 draws a logo on an installed MCP server only when its URL equals an entry of the Library, and drops the icon of the server's settings. Servers behind the token proxy never match, so every card showed the generic symbol. The image build now patches the frontend to keep the icon and show it when no Library entry matches; the build fails when the frontend no longer has the expected shape.

Icons come from the RemoteMCPServer annotation openhands.dev/icon, from a list of known names (now with kagent-tools, librenms, memory, netbox, kubernetes, bitwarden and others, and a working netbox logo), from the new icon field of mcpServers, and the Kubernetes logo for the built-in kubernetes server.

Upgrade

A new image builds on the next sync; an instance with build.enabled false keeps its image.

0.6.0

Released 2026-10-24.

Changes

The gateway is a provider under Settings > LLM

Agent Canvas 1.33 lists models as profiles of a provider connection. The chart only wrote the plain model settings, so Settings > LLM showed no provider. With gateway.upstream set, the seed now creates the provider connection llm.connection.name (default Gateway) to the token proxy, one profile per model in llm.model and llm.models, and activates the profile of llm.model. The profile default that earlier versions left without a connection is removed.

SHELL and the rest of the opencode-agent toolset

The image sets SHELL=/bin/bash, which Claude Code needs for its Bash tool. The build adds ripgrep, xxd, vi, lsof, psmisc, procps, iproute2, ping, traceroute, whois, bc, dos2unix, gnupg, sysstat, cpio and file from Debian, and ruff from PyPI, so the image carries what the opencode-agent image has. opencode itself stays out: its build is for musl.

Upgrade

A new image builds on the next sync. The pod restarts because the seeded values change, and the seed adds the connection and profiles to the existing settings. An instance with build.enabled false keeps its image: point image.tag at a 0.6.0 build to get the tools.

0.5.0

Released 2026-10-24.

Changes

A2A server, one agent per conversation

A new sidecar makes the agent an A2A server, so other agents and kagent can chat with it. Every A2A context becomes its own conversation, and every titled conversation started in the UI is also a sub-agent named / at /agents/<slug>/, which continues that conversation. Calls need a bearer token of the tenant (ServiceAccount or OIDC with the tenant in its groups claim) or the optional static credential.</p> <h4 id="listed-in-kagent">Listed in kagent<a class="headerlink" href="#listed-in-kagent" title="Permanent link">¶</a></h4> <p>With kagent installed the chart registers a BYO Agent named like the release. kagent runs it as a small relay pod in front of the sidecar, because the agent-server listens on loopback only. Set a2a.tenantAuth.oidcIssuers to the realm kagent logs users in with, or kagent's chats get 401.</p> <h2 id="040">0.4.0<a class="headerlink" href="#040" title="Permanent link">¶</a></h2> <p>Released 2026-10-24.</p> <h3 id="changes_13">Changes<a class="headerlink" href="#changes_13" title="Permanent link">¶</a></h3> <h4 id="openhands-040-publishes-ui-mcp-servers-to-the-team-42">openhands 0.4.0 publishes UI MCP servers to the team (<a href="https://github.com/hoolia/apps.openshift.eu/pull/42">#42</a>)<a class="headerlink" href="#openhands-040-publishes-ui-mcp-servers-to-the-team-42" title="Permanent link">¶</a></h4> <p>A remote MCP server a user adds in the OpenHands UI now becomes an AgentgatewayBackend of the project, with the headers OpenHands would send in a Secret owned by the backend. The platform turns it into a gateway endpoint and a RemoteMCPServer, so every agent of the team discovers it; removing it in the UI removes the backend. OAuth, in-pod and local servers stay personal, and the platform's admission policy decides which hosts a project may reach. The UI entry's description shows the outcome.</p> <h2 id="030">0.3.0<a class="headerlink" href="#030" title="Permanent link">¶</a></h2> <p>Released 2026-10-24.</p> <h3 id="changes_14">Changes<a class="headerlink" href="#changes_14" title="Permanent link">¶</a></h3> <h4 id="openhands-030-agent-canvas-1330-oauth-mcp-redirect-to-the-route-38">openhands 0.3.0: Agent Canvas 1.33.0, OAuth MCP redirect to the route (<a href="https://github.com/hoolia/apps.openshift.eu/pull/38">#38</a>)<a class="headerlink" href="#openhands-030-agent-canvas-1330-oauth-mcp-redirect-to-the-route-38" title="Permanent link">¶</a></h4> <p>Agent Canvas 1.33.0 ships agent-server 1.58.1 and automation 1.25.1. The settings, MCP, secrets and agent-profile endpoints the seed and mcp-sync scripts call are unchanged, and the entrypoint still carries the two STATIC_SERVER_BIND_HOST lines the build rewrites to loopback, so the Containerfile and scripts need no change.</p> <p>The upstream entrypoint now defaults LOG_LEVEL to WARNING, which only quiets the backends' startup output.</p> <p>OpenHands registers http://localhost:<random port>/callback as the OAuth redirect of an MCP server added in the UI, which the browser of a hosted agent never reaches, so no OAuth MCP could be installed. With route.host set, module mcp_oauth_redirect (loaded by the agent-server like canvas_ui_tool) registers https://<route.host>/mcp-oauth/callback instead and listens on a fixed loopback port that the static server routes to, behind the OpenShift login. One OAuth login runs at a time.</p> <p>mcp-sync also gives discovered servers an icon: annotation openhands.dev/icon on the RemoteMCPServer, else the logo of a known name such as gitlab or grafana.</p> <h2 id="020">0.2.0<a class="headerlink" href="#020" title="Permanent link">¶</a></h2> <p>Released 2026-10-23.</p> <h3 id="changes_15">Changes<a class="headerlink" href="#changes_15" title="Permanent link">¶</a></h3> <h4 id="openhands-020-discovers-remotemcpservers-37">openhands 0.2.0 discovers RemoteMCPServers (<a href="https://github.com/hoolia/apps.openshift.eu/pull/37">#37</a>)<a class="headerlink" href="#openhands-020-discovers-remotemcpservers-37" title="Permanent link">¶</a></h4> <p>Sidecar mcp-sync turns every kagent RemoteMCPServer of the project into an MCP server of the agent and keeps the list in step: gateway URLs go through the token proxy, servers that need Secret headers are skipped, entries a user made are left alone. It also writes the git token as GITLAB_TOKEN into the secrets store, which is what turns on the UI's git provider features.</p> <p>The ClusterRole that reads RemoteMCPServers belongs to kagent, which owns the CRD; the chart only binds it (mcpDiscovery.clusterRole, default kagent-remotemcpserver-view) in its own namespace.</p> <h2 id="013">0.1.3<a class="headerlink" href="#013" title="Permanent link">¶</a></h2> <p>Released 2026-10-22.</p> <h3 id="changes_16">Changes<a class="headerlink" href="#changes_16" title="Permanent link">¶</a></h3> <h4 id="bind-login-groups-to-a-clusterrole">Bind login groups to a ClusterRole<a class="headerlink" href="#bind-login-groups-to-a-clusterrole" title="Permanent link">¶</a></h4> <p>Kubernetes refuses a Role that grants permissions the creator does not hold. A project admin, and the project's Argo CD with admin, do not hold the verb login, so the group lockdown of 0.1.2, 0.3.5 and 0.2.1 failed to sync for them ("attempting to grant RBAC permissions not currently held"). oauth.loginClusterRole names a ClusterRole holding that verb; allowedGroups are bound to it in a RoleBinding <name>-app-login, which needs only bind on that ClusterRole. roleRef is immutable, hence the new binding name. Empty keeps the chart's own Role for cluster admins.</p> <h2 id="012">0.1.2<a class="headerlink" href="#012" title="Permanent link">¶</a></h2> <p>Released 2026-10-22.</p> <h3 id="changes_17">Changes<a class="headerlink" href="#changes_17" title="Permanent link">¶</a></h3> <h4 id="openhands-012-agent-canvas-1300-owner-or-group-login-lockdown-34">openhands 0.1.2 (Agent Canvas 1.30.0), owner or group login lockdown (<a href="https://github.com/hoolia/apps.openshift.eu/pull/34">#34</a>)<a class="headerlink" href="#openhands-012-agent-canvas-1300-owner-or-group-login-lockdown-34" title="Permanent link">¶</a></h4> <p>Agent Canvas 1.30.0 binds the frontend through STATIC_SERVER_BIND_HOST instead of a literal --host ::, so the loopback patch rewrites both assignments (the build still asserts two hits). The volume claim template drops the version label: Kubernetes refuses any change there, so every appVersion bump failed the StatefulSet update. A cookie secret that is not 16, 24 or 32 characters long is hashed to 32, as in opencode-agent and openwebui, so a 48-character generated value works.</p> <p>opencode-agent 0.3.5 and openwebui 0.2.1: with neither oauth.ownerEmail nor person.email set, the login proxy drops the emails file and admits everyone who may get services/proxy on the UI Service, which admin and edit grant. A team instance from a managed Template needs no e-mail. Open WebUI then allows sign-up with role user; the first user is admin. With an owner set the render is unchanged.</p> <p>Every chart now offers two lockdowns. oauth.ownerEmail admits that one user (emails file). oauth.allowedGroups admits only members of those groups: the login Role grants the verb login on services/proxy of the UI Service and the login proxy checks that verb. The previous check, get on services/proxy, is also held by admin and edit, so any project editor passed a group lockdown. With neither set, project editors log in. openhands gains oauth.ownerEmail.</p> <p>Verified on berry-ai with the authorization.openshift.io SAR the proxy sends: a grncloud-engineering member is allowed login, a grncloud-admins project admin and an outsider are refused, the same admin is allowed get.</p> <p>The group lockdown checks the verb login, which only the login Role grants; the tests asserted the previous verb get.</p> <h2 id="011">0.1.1<a class="headerlink" href="#011" title="Permanent link">¶</a></h2> <p>Released 2026-10-22.</p> <h3 id="changes_18">Changes<a class="headerlink" href="#changes_18" title="Permanent link">¶</a></h3> <h4 id="openhands-011-preconfigures-agents">openhands 0.1.1 preconfigures agents<a class="headerlink" href="#openhands-011-preconfigures-agents" title="Permanent link">¶</a></h4> <p>The first-run wizard asked for an LLM and overwrote the seeded model with openai/gpt-5.6-sol; the image now marks it done in the page (the frontend's only condition is localStorage openhands-onboarded).</p> <p>agentSecrets seeds the environment of the ACP agents into the agent-server secrets store, so Claude Code and Codex no longer ask for keys and base URLs; agentProfiles seeds selectable ACP agent profiles.</p> <p>Verified on berry-ai (sam-10x): fresh browser opens without wizard; Claude Code answers through /anthropic on the gateway; Codex answers with a provider config that turns off its WebSocket transport.</p> <h2 id="010">0.1.0<a class="headerlink" href="#010" title="Permanent link">¶</a></h2> <p>Released 2026-10-22.</p> <h3 id="changes_19">Changes<a class="headerlink" href="#changes_19" title="Permanent link">¶</a></h3> <h4 id="add-openhands-010-chart-30">Add openhands 0.1.0 chart (<a href="https://github.com/hoolia/apps.openshift.eu/pull/30">#30</a>)<a class="headerlink" href="#add-openhands-010-chart-30" title="Permanent link">¶</a></h4> <p>Shared OpenHands Agent Canvas behind an OpenShift login. One StatefulSet runs agent-canvas, oauth-proxy and a token proxy that sends the pod's ServiceAccount token to an OpenAI-compatible gateway for models and MCP.</p> <p>The upstream chart cannot carry sidecars and pins UID 10001, which restricted-v2 rejects. This chart builds the upstream image with an OpenShift toolset, runs it with an arbitrary UID, keeps the session key out of the page, seeds model and MCP settings through the agent-server API once per value change, and admits only groups that hold create on services/proxy (cluster-reader holds get).</p> <p>Verified live on berry-ai as release sam-10x: restricted-v2, login redirect, completion through the gateway, MCP tools listed, restart keeps conversations and settings.</p> <p>build.toolsImage copies every binary of an image such as opencode-agent (its /usr/local/bin except OpenCode, Go toolchain, /tools, sonar-scanner and Ansible collections), so both agents carry the same CLI versions. Python and npm tools are tied to that image's Alpine runtimes and are reinstalled on Debian at pinned versions from build.tools; the build runs one check per tool as a non-root UID and fails on any miss.</p> <p>kubernetesMcpServer adds the local stdio MCP server the opencode agents use. The agent-server starts stdio servers with a reduced environment, so the seed hook hands KUBERNETES_SERVICE_HOST and _PORT through passEnv. mcpServers entries take enabled, because the agent-server lists all servers within one 30 s budget and one slow server drops them all.</p> <p>The login check is back to get on services/proxy: cluster-reader does not hold it; the earlier switch to create rested on a malformed test.</p> <p>Verified live on berry-ai as sam-10x: every tool check passes, kubernetes and public MCP tools work in a UI conversation, PDF upload and parsing work in the UI.</p> <p>A new browser met "No backend configured" and had to paste the session API key: the upstream entrypoint binds the frontend to ::, and then it does not embed the key. The image now rewrites that flag to 127.0.0.1 (the build fails when the upstream text is not found). On loopback the frontend embeds the key, so a logged-in browser gets a connected backend, and only oauth-proxy in the pod can fetch that page.</p> <p>The agent-server also listens on 127.0.0.1, so the probes run curl in the container. Verified on berry-ai: neighbour pod sees 8000 and 18000 refused; fresh browser opens without a backend prompt and chats.</p> </article> </div> <script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script> </div> <button type="button" class="md-top md-icon" data-md-component="top" hidden> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg> Back to top </button> </main> <footer class="md-footer"> <div class="md-footer-meta md-typeset"> <div class="md-footer-meta__inner md-grid"> <div class="md-copyright"> <div class="md-copyright__highlight"> GRN.CLOUD B.V. </div> Made with <a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener"> Material for MkDocs </a> </div> </div> </div> </footer> </div> <div class="md-dialog" data-md-component="dialog"> <div class="md-dialog__inner md-typeset"></div> </div> <script id="__config" type="application/json">{"annotate": null, "base": "../../..", "features": ["navigation.indexes", "navigation.sections", "navigation.top", "search.highlight", "search.suggest", "content.code.copy", "content.action.edit"], "search": "../../../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script> <script src="../../../assets/javascripts/bundle.79ae519e.min.js"></script> </body> </html>