fix(hosting): make the Helm chart deploy cleanly on a managed Kubernetes (GKE Autopilot) - #6690
fix(hosting): make the Helm chart deploy cleanly on a managed Kubernetes (GKE Autopilot)#6690mmabrouk wants to merge 18 commits into
Conversation
The runner Deployment and the agenta.agentRunner.servicesEnv helper asked for AGENTA_RUNNER_TOKEN from a Secret named by agenta.fullname. When the operator sets secrets.existingSecret the chart creates no such Secret, so the runner, the api and the services pods failed with CreateContainerConfigError. Both places now go through agenta.secretName, which is what every other secret reference in the chart already uses. An explicit agentRunner.auth.tokenSecretRef still wins. Adds tests/test_existing_secret.py. It renders with secrets.existingSecret and checks that no pod spec references the chart-managed Secret name. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
web-deployment.yaml and web-mobile-deployment.yaml read agenta.webUrl, agenta.apiUrl and agenta.servicesUrl straight from the values, with an empty string as the fallback. Every other workload uses the agenta.webUrlEffective, agenta.apiUrlEffective and agenta.servicesUrlEffective helpers, which fall back to ingress.host. An operator who relied on that derivation got empty URLs in the browser bundle, which breaks OAuth redirects, links in email and any absolute URL the app builds. Both templates now use the same helpers. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The api, web, web-mobile and services Service templates had no way to carry annotations. A managed ingress needs them: GKE reads cloud.google.com/neg and cloud.google.com/backend-config off the Service. Each template now renders <component>.service.annotations under metadata.annotations when the map is not empty. The key is documented on the shared component definition in values.schema.json. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
values.schema.json set additionalProperties:false on ingress.paths and listed only api, services and web. ingress.yaml renders a fourth path from the agenta.ingress.paths.webMobile helpers, so ingress.paths.webMobile was rejected at install time. The two helpers also ignored the values and returned constants. The schema now lists webMobile with the same shape as the other three, and the helpers read it with the old constants as defaults. The path should stay /m because the mobile image is built with that basePath. pathType is now overridable, which a managed ingress can need. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
tests/test_web_mobile.py did not set agenta.servicesInternalKey, so every render it made failed on agenta.validateRequiredSecrets and the test errored out. The break predates this branch. Add the key to the shared render arguments. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The public URL validation told the operator to write 'tls: true'. The schema types ingress.tls as an array and ingress.yaml passes it through with toYaml, so that advice fails schema validation. Show the list form instead, and say that any non-empty list switches the derived URLs to https. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
With store.enabled=true the runner Deployment added the SYS_ADMIN capability and a hostPath volume for /dev/fuse. GKE Autopilot rejects both, and the only way to avoid them was to set agentRunner.securityContext, which also has to replace the whole security context. agentRunner.fuse.enabled now gates the capability and the volume together. It defaults to true, so nothing changes for current users. An explicit agentRunner.securityContext still wins, as before. values.yaml tells Autopilot users to set it false and run sandboxes on the Daytona provider. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The migration Job is a post-install/post-upgrade hook because a pre-install hook would deadlock on the bundled PostgreSQL StatefulSet, which does not exist yet at that point. That reasoning does not hold with an external database, and there the app pods start against an unmigrated schema for no reason. alembic.hookPhase now picks the phase. It defaults to post, the behavior the chart always had. Set it to pre with postgresql.enabled=false to render pre-install,pre-upgrade. The delete policy stays before-hook-creation, and both Secrets sit at hook weight -5, so they still land before the Job at weight 0. A bad value is rejected by the schema, and by agenta.validateAlembicHookPhase when schema validation is skipped. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The Ingress only ever routed the four services the chart owns. An operator who needs one more route on the same host, such as /llm in front of a gateway, had to write a second Ingress by hand through extraObjects. ingress.extraPaths appends items to the main host rule, after the built-in paths. ingress.extraHosts adds whole rules, one per hostname, each with its own path list. Both take the same item shape: path, pathType (default Prefix), serviceName, servicePort. A numeric servicePort renders as port.number and a string as port.name. The Ingress used to render only when a chart component was enabled. It now also renders for extra routes alone, and the main host rule is skipped when it would have no paths. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
Three fixes to both hosting/kubernetes/oss/values.oss.example.yaml and hosting/kubernetes/ee/values.ee.example.yaml. Remove seven worker components no template reads: workerEvaluations, workerTracing, workerWebhooks, workerEvents, workerRecords, workerTriggers and workerInteractions. The chart has workerStreams and workerQueues, which the examples never showed. Show those instead. Correct the ingress comment. It said /api is prefix-stripped. The API is mounted at /api through SCRIPT_NAME and the FastAPI root_path, so the prefix must reach it; docker-compose says the same. Only /services is stripped, so /api is dropped from the example StripPrefix Middleware. Say that store.enabled defaults to false, and that an external store also needs store.seaweedfs.enabled: false, which otherwise defaults to true and deploys a StatefulSet nothing reads. The audit named only the EE file. The OSS file carried the same three defects, so both are fixed together. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The chart tests are standalone scripts whose checks all live in main(), so pytest collected the files, found no test function and reported success without running anything. Each file now has a thin test function that calls main(). Both entry points still work: uv run <file> and pytest tests/. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
hosting/kubernetes had no entry point. The chart is large and the keys a managed cluster needs are spread across values.schema.json and the two example files. The README says what the chart deploys, which values are required, and gives the minimal delta for an external PostgreSQL and a managed ingress on GKE: the external database block, alembic.hookPhase, the ingress class and annotations, the per-component Service annotations, ingress.extraPaths and extraHosts, agentRunner.fuse.enabled on Autopilot, and the object store toggles. It links to docs/docs/self-host/deploy/03-deploy-to-kubernetes.mdx for the walkthrough. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The store env block pointed AGENTA_STORE_ENDPOINT_URL at the in-cluster SeaweedFS Service whenever the bundled store was enabled, and read store.endpointUrl only when it was not. A Daytona sandbox runs outside the cluster and cannot resolve that Service name, so the sandbox could not reach the store at all. An explicit store.endpointUrl now wins in both cases. The internal Service URL stays the fallback when the key is unset, so a cluster-only deployment is unchanged. Compose solves this the same way, with AGENTA_STORE_TRAEFIK_ENABLE and AGENTA_STORE_DOMAIN. The SeaweedFS pod is untouched: it never reads the endpoint URL. The runner renders no store variables at all, so there was nothing to change there. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The bundled SeaweedFS gateway was reachable only from inside the cluster. A Daytona sandbox needs a hostname it can resolve, and the chart had no way to give it one short of a hand-written Ingress in extraObjects. store.seaweedfs.ingress renders one: enabled (false by default), className, annotations, host and a tls list passed through verbatim. It routes / with pathType Prefix to the SeaweedFS Service on its named s3 port, and it renders only when the bundled SeaweedFS is deployed. host is required once enabled. Pair it with store.endpointUrl set to the same hostname, so every pod hands out the URL the sandbox can reach. The SeaweedFS Service already exposed a named s3 port, so it needed no change. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The runner binds 127.0.0.1 unless AGENTA_RUNNER_HOST says otherwise, and the chart never set it. In a pod the kubelet connects over the pod IP, so the startup probe on :8765/health got connection refused forever and the runner never became ready. Confirmed on a live GKE cluster; the container logged 'http server listening on 127.0.0.1:8765'. The runner Deployment now renders AGENTA_RUNNER_HOST from agentRunner.host, default 0.0.0.0. Setting AGENTA_RUNNER_HOST in the agentRunner.env map suppresses the chart's entry instead of adding a duplicate, which is how AGENTA_RUNNER_REPLICA_ID and PI_CODING_AGENT_DIR already behave. tests/test_runner_secret_absence.py now requires the variable, checks the value is 0.0.0.0, checks both override paths, and checks it is never set twice. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
<component>.env takes plain strings only, so an operator could not point a variable at a Secret key the chart does not model. The EE api needs AGENTA_STARTER_CREDITS_BRIDGE_MASTER_KEY from the platform Secret, and there was no way to ask for it. Every workload now also takes extraEnv, a raw list of Kubernetes env entries rendered with toYaml so valueFrom works, and envFrom, a raw list rendered on the container. Both come after the chart's own variables, so a later entry wins. Covered: api, services, web, webMobile, cron, workerStreams, workerQueues, agentRunner, alembic and supertokens. The runner carries a warning in the template and the schema. Its environment is narrow on purpose, because a local harness shares the container and can read /proc. envFrom pulls a whole Secret and defeats tests/test_runner_secret_absence.py, which can only see named entries. A render with no extraEnv or envFrom is byte for byte what it was before. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
Two corrections from a live GKE cluster. GKE does not act on spec.ingressClassName. The controller reads the legacy kubernetes.io/ingress.class annotation, and without it the Ingress gets no controller events and never gets an IP. The chart always renders ingressClassName, so the operator sets both. Set the NEG annotation on each Service yourself. Autopilot adds it only when it creates the Service, so a helm upgrade --force recreates Services without it. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
The Ingress always rendered spec.ingressClassName, defaulting to traefik. The GKE controller ignores that field: an Ingress carrying className gce gets no controller events and never gets an IP, even with an IngressClass object present. It reacts only to the legacy kubernetes.io/ingress.class annotation, and the chart gave the operator no way to leave the field out. ingress.className: "" now renders no ingressClassName. The helper checks hasKey instead of using default, which would turn an empty string back into traefik. An unset key still means traefik, so nothing changes for current users, and a default render is byte for byte what it was. The README, the schema and both example files carry the GKE note. Claude-Session: https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Mahmoud Mabrouk seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
📝 SummarySummary by CodeRabbit
WalkthroughThe Helm chart adds configurable environment injection, migration hook phases, runner networking and FUSE controls, ingress routes, SeaweedFS exposure, service annotations, existing-secret handling, URL fallback fixes, schemas, tests, and Kubernetes deployment documentation. ChangesKubernetes Helm deployment configuration
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🟡 Moderate · up to Several valid chart configurations can fail installation or break runner operation, while configuration guidance can produce ineffective overrides. These issues should be corrected before merge. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Advanced
Run ID: 8a1bc4a9-d957-47dc-9653-083e140d21ea
📒 Files selected for processing (27)
hosting/kubernetes/README.mdhosting/kubernetes/ee/values.ee.example.yamlhosting/kubernetes/helm/templates/_helpers.tplhosting/kubernetes/helm/templates/_validations.tplhosting/kubernetes/helm/templates/alembic-job.yamlhosting/kubernetes/helm/templates/api-deployment.yamlhosting/kubernetes/helm/templates/api-service.yamlhosting/kubernetes/helm/templates/cron-deployment.yamlhosting/kubernetes/helm/templates/ingress.yamlhosting/kubernetes/helm/templates/runner-deployment.yamlhosting/kubernetes/helm/templates/seaweedfs-ingress.yamlhosting/kubernetes/helm/templates/secrets.yamlhosting/kubernetes/helm/templates/services-deployment.yamlhosting/kubernetes/helm/templates/services-service.yamlhosting/kubernetes/helm/templates/supertokens-deployment.yamlhosting/kubernetes/helm/templates/web-deployment.yamlhosting/kubernetes/helm/templates/web-mobile-deployment.yamlhosting/kubernetes/helm/templates/web-mobile-service.yamlhosting/kubernetes/helm/templates/web-service.yamlhosting/kubernetes/helm/templates/worker-queues-deployment.yamlhosting/kubernetes/helm/templates/worker-streams-deployment.yamlhosting/kubernetes/helm/tests/test_existing_secret.pyhosting/kubernetes/helm/tests/test_runner_secret_absence.pyhosting/kubernetes/helm/tests/test_web_mobile.pyhosting/kubernetes/helm/values.schema.jsonhosting/kubernetes/helm/values.yamlhosting/kubernetes/oss/values.oss.example.yaml
Included review availability: Your plan provides up to 8 included reviews per hour; 1 remains after this review.
| {{- with $api.envFrom }} | ||
| envFrom: | ||
| {{- toYaml . | nindent 12 }} | ||
| {{- end }} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Correct the envFrom precedence comments
Kubernetes gives duplicate names in explicit env entries precedence over envFrom, regardless of template order. Update the comments in api-deployment.yaml, alembic-job.yaml, cron-deployment.yaml, services-deployment.yaml, supertokens-deployment.yaml, web-deployment.yaml, worker-queues-deployment.yaml, worker-streams-deployment.yaml, and web-mobile-deployment.yaml to state that envFrom supplies only names absent from env. Otherwise, an operator-supplied Secret or ConfigMap retains the chart-generated value for colliding names. Keep extraEnv as the override mechanism because it emits explicit env entries and supports valueFrom.
| {{- range .paths }} | ||
| {{- include "agenta.ingress.extraPath" . | nindent 10 }} |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🤖 get_repo_knowledge executed:
get_repo_knowledge Agenta-AI/agenta /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/learnings /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/conventions
Length of output: 43621
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- changed hunk ---'
git diff -- hosting/kubernetes/helm/templates/ingress.yaml hosting/kubernetes/helm/values.schema.json
printf '%s\n' '--- ingress template ---'
cat -n hosting/kubernetes/helm/templates/ingress.yaml | sed -n '1,130p'
printf '%s\n' '--- schema ingress section ---'
rg -n -A80 -B20 '"extraHosts"|"paths"|"extraPath"' hosting/kubernetes/helm/values.schema.json
printf '%s\n' '--- values references ---'
rg -n -A30 -B10 'extraHosts|extraPath' hosting/kubernetes/helmRepository: Agenta-AI/agenta
Length of output: 26846
🌐 Web query:
site:kubernetes.io Ingress networking.k8s.io/v1 HTTPIngressRuleValue paths minItems 1 validation
💡 Result:
In the Kubernetes networking.k8s.io/v1 Ingress API, the paths field within an HTTPIngressRuleValue is an array of HTTPIngressPath objects [1]. This array is required to contain at least one element [1]. Validation for this requirement is enforced by the Kubernetes API server [2]. While the specific minItems validation tag may not always be explicitly exposed in high-level documentation summaries, the API schema mandates that an Ingress rule must define paths to map requests to backends [1]. If a rule is provided without any paths, the Ingress object will fail validation upon submission because the list is empty, which violates the structural requirements of the HTTPIngressRuleValue type [1]. In practice, if you attempt to create an Ingress resource with an empty paths list, the Kubernetes API server will reject the request with a validation error [2][3]. Users should ensure that every http block within an Ingress rule includes at least one path entry along with its associated pathType and backend [1][4][5].
Citations:
- 1: https://kubernetes.io/docs/reference/kubernetes-api/networking/ingress-v1/
- 2: https://kubernetes.io/docs/reference/using-api/declarative-validation/
- 3: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_create/kubectl_create_ingress/
- 4: https://kubernetes.io/docs/concepts/services-networking/ingress/
- 5: https://v1-36.docs.kubernetes.io/docs/concepts/services-networking/ingress/
Reject empty extraHosts[].paths.
The schema permits paths: [], but the template renders an Ingress rule with an empty http.paths list. Kubernetes rejects this rule. Add minItems: 1 to hosting/kubernetes/helm/values.schema.json, or reject the empty array before rendering.
| {{- toYaml . | nindent 12 }} | ||
| {{- end }} | ||
| {{- if and (eq (include "agenta.store.enabled" .) "true") (not $runner.securityContext) }} | ||
| {{- if and (eq (include "agenta.store.enabled" .) "true") (eq (include "agenta.agentRunner.fuse.enabled" .) "true") (not $runner.securityContext) }} |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Render the /dev/fuse mount independently of runner.securityContext.
When store.enabled and agentRunner.fuse.enabled are true, a custom runner.securityContext suppresses both the /dev/fuse mount and the SYS_ADMIN default. The custom context is the capability override, while agentRunner.fuse.enabled controls FUSE support for local geesefs sandboxes. Remove (not $runner.securityContext) from both volume conditions so a custom context that grants the required capability does not break local sandbox operation.
| {{- with $ingress.tls }} | ||
| tls: | ||
| {{- toYaml . | nindent 4 }} | ||
| {{- end }} |
There was a problem hiding this comment.
🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🤖 get_repo_knowledge executed:
get_repo_knowledge Agenta-AI/agenta /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/learnings /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/conventions
Length of output: 5732
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- ingress template ---'
cat -n hosting/kubernetes/helm/templates/seaweedfs-ingress.yaml
printf '%s\n' '--- relevant values ---'
rg -n -C 8 'seaweedfs|endpointUrl|tls:' hosting/kubernetes/helm/values.yaml
printf '%s\n' '--- endpointUrl and ingress references ---'
rg -n -C 4 'endpointUrl|seaweedfs\.ingress|ingress\.tls' hosting/kubernetes/helm --glob '!values.schema.json'Repository: Agenta-AI/agenta
Length of output: 13316
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- store schema and credentials ---'
sed -n '210,330p' hosting/kubernetes/helm/values.schema.json
sed -n '930,980p' hosting/kubernetes/helm/templates/_helpers.tpl
printf '%s\n' '--- store endpoint and credential consumers ---'
rg -n -C 5 'AGENTA_STORE_(ACCESS|SECRET|ENDPOINT)|accessKey|secretKey|endpointUrl' hosting/kubernetes/helm/templates hosting/kubernetes/helm/values.schema.jsonRepository: Agenta-AI/agenta
Length of output: 50372
Security Misconfiguration
Reachability: External
Exploitability: Moderate
CWE: CWE-319 — Cleartext Transmission of Sensitive Information
Require HTTPS for the public SeaweedFS route.
When store.seaweedfs.ingress.enabled is true and tls is omitted, the Ingress exposes the S3 gateway without spec.tls. Reject HTTP store.endpointUrl values or require TLS for this route. Update hosting/kubernetes/helm/values.yaml to show an HTTPS endpoint and matching tls configuration.
| "type": "object", | ||
| "additionalProperties": false, | ||
| "description": "Optional public route to the bundled S3 gateway, for clients outside the cluster such as a Daytona sandbox. Set store.endpointUrl to the same hostname so every pod hands out that URL.", | ||
| "required": ["host"], |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Make host conditional on enabled for the SeaweedFS ingress.
"required": ["host"] applies whenever the store.seaweedfs.ingress object exists. The description says host is "Required when enabled", but the schema does not express that condition. An operator who only turns the route off, for example --set store.seaweedfs.seaweedfs.ingress.enabled=false or --set store.seaweedfs.ingress.enabled=false, gets a schema validation failure asking for a host they do not need.
Use if/then so the requirement applies only when enabled is true.
🔧 Proposed schema fix
"additionalProperties": false,
"description": "Optional public route to the bundled S3 gateway, for clients outside the cluster such as a Daytona sandbox. Set store.endpointUrl to the same hostname so every pod hands out that URL.",
- "required": ["host"],
+ "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] },
+ "then": { "required": ["host"] },
"properties": {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "required": ["host"], | |
| "if": { "properties": { "enabled": { "const": true } }, "required": ["enabled"] }, | |
| "then": { "required": ["host"] }, |
| # The chart has two worker deployments. `streams` and `queues` are selector | ||
| # lists; an empty list means every loop of that kind. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Worker selector documented as a list in both example values files. The chart renders AGENTA_WORKER_STREAMS and AGENTA_WORKER_QUEUES from a single string (default "" $workerStreams.streams | quote), but both comments call streams and queues "selector lists" and say an empty list runs every loop. An operator who sets streams: [] renders the literal "[]".
hosting/kubernetes/oss/values.oss.example.yaml#L483-L484: describestreamsandqueuesas comma-separated strings, and say an empty string means every loop of that kind.hosting/kubernetes/ee/values.ee.example.yaml#L486-L487: apply the same wording change so both examples match the template contract.
📍 Affects 2 files
hosting/kubernetes/oss/values.oss.example.yaml#L483-L484(this comment)hosting/kubernetes/ee/values.ee.example.yaml#L486-L487
| Nothing constrains the keys under `postgresql`, so `helm install` accepts a | ||
| misspelled one and falls back to the default. Render the chart and read the | ||
| `POSTGRES_URI_CORE` value back before you install. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Correct the schema-validation claims.
The chart schema sets additionalProperties: true at both the root and postgresql levels. Helm accepts unknown or misspelled keys, and templates read only declared keys, so the default remains active. Update lines 11 and 279-280 to describe validation of declared fields, and keep the warning at lines 85-87.
Why
We deployed the chart on GKE Autopilot with an external Cloud SQL and found a set of defects and missing knobs. Each one either broke the install outright or forced a manual step after every
helm upgrade. All of them were found on a live cluster; none is theoretical.What changed
Correctness fixes
secrets.existingSecretwas ignored forAGENTA_RUNNER_TOKENin the runner Deployment and in the helper that feeds api and services; those three pods failed on a missing Secret. New test covers it.AGENTA_RUNNER_HOST, bound127.0.0.1, and failed its startup probe forever. It now binds0.0.0.0by default (agentRunner.host).AGENTA_STORE_ENDPOINT_URLignoredstore.endpointUrlwhen SeaweedFS was bundled. Daytona sandboxes need the public URL; the value now wins over the internal Service URL.ingress.paths.webMobilewas rendered but rejected by the schema. The schema and the helpers agree now.spec.ingressClassNameis rendered only wheningress.classNameis non-empty. The GKE controller ignores that field and reads only the legacykubernetes.io/ingress.classannotation.ingress.tlsvalidation message described a shape the schema rejects.New operator knobs
<component>.service.annotationson the four Services (GKEBackendConfigand NEG annotations).agentRunner.fuse.enabledto turn off theSYS_ADMINcapability and the/dev/fusehostPath, which Autopilot rejects.alembic.hookPhase: preto run migrations before the pods start when the database is external.ingress.extraPathsandingress.extraHosts.store.seaweedfs.ingressfor a public store hostname.<component>.extraEnvand<component>.envFromon every workload.Docs and tests
hosting/kubernetes/README.mdwith the external-Postgres and managed-ingress deltas./apistripping comment matches the chart.Chart version, appVersion and default ports are unchanged. A default render is byte for byte identical to the base except for the web URL fix.
How to verify
Live: the
ossandstagingstages on the GKE cluster inagenta-stagedeploy from this branch.https://claude.ai/code/session_01GZqpvjwLHgVnMrpKH8hzq8