Summary
Add agents.<name>.slack.existingSecret to the openab chart. When set, the chart references the named Kubernetes Secret for SLACK_BOT_TOKEN and SLACK_APP_TOKEN instead of creating a chart-managed Secret from Helm values.
This adapts the existingSecret pattern introduced in the openab-telegram chart (PR #873) to the multi-agent structure of the openab chart — scoped per-agent rather than chart-level.
Motivation
GitOps / External Secrets Operator deployments. In GitOps environments, secrets are typically managed by an operator such as External Secrets Operator (ESO) that syncs values from an external store (AWS Secrets Manager, Vault, etc.) into Kubernetes Secrets on a refresh interval.
The openab chart already supports this pattern for ANTHROPIC_API_KEY via secretEnv (rendered as valueFrom.secretKeyRef). Slack tokens currently cannot follow the same path because the chart creates its own Secret from Helm values, requiring them to be passed at helm install/helm upgrade time:
External Secret Store
├── ANTHROPIC_API_KEY → secretEnv → valueFrom.secretKeyRef ✅
├── SLACK_BOT_TOKEN → must be passed as Helm value ❌
└── SLACK_APP_TOKEN → must be passed as Helm value ❌
This means every token rotation requires a Helm re-apply, blocking automation and making platform engineers a bottleneck for what should be a self-service operation.
With existingSecret, all credentials follow the same ESO path:
External Secret Store
├── ANTHROPIC_API_KEY → secretEnv → valueFrom.secretKeyRef ✅
├── SLACK_BOT_TOKEN → existingSecret → valueFrom.secretKeyRef ✅
└── SLACK_APP_TOKEN → existingSecret → valueFrom.secretKeyRef ✅
When the external store is updated, ESO syncs the Kubernetes Secret within its refresh interval. The pod picks up the new tokens on its next restart — no Helm re-apply, no manual intervention.
Self-Service Bot Platform Context
We deploy OpenAB as a multi-tenant platform where each team owns a skill repo — a Git repository containing a SKILL.md that defines the bot's behaviour. When a team merges a change to SKILL.md, a CI job automatically restarts the pod (~90 seconds end-to-end). Teams iterate on bot behaviour with zero platform involvement.
The infrastructure workload (namespace, ServiceAccount, ESO ExternalSecret, Helm release) is provisioned once via a standard Terraform module. After that initial setup, the platform team should never need to touch it again — but today they must re-apply whenever Slack tokens rotate, because of helm_release_set_sensitive.
existingSecret closes this last gap:
Team rotates Slack token in external store
→ ESO syncs K8s Secret (automatic, within refresh interval)
→ Team restarts pod (or waits for next skill update to trigger restart)
→ Bot reconnects with new token
→ Platform team involvement: zero
Proposed Change
values.yaml
agents:
claude:
slack:
enabled: false
botToken: "" # used only when existingSecret is not set
appToken: "" # used only when existingSecret is not set
existingSecret: "" # if set: reference this K8s Secret for slack-bot-token
# and slack-app-token; skip chart-managed Secret
templates/secret.yaml (change)
The current $hasSlack variable is:
$hasSlack := and ($cfg.slack).enabled (or ($cfg.slack).botToken ($cfg.slack).appToken)
When existingSecret is set and both botToken/appToken are left empty, $hasSlack is already false — for Slack-only agents, the chart-managed Secret is not created at all. For agents that also use Discord, STT, or gateway, the chart-managed Secret is still created for those tokens; only the Slack entries are suppressed.
Guard the Slack entries explicitly:
{{- if and ($cfg.slack).enabled ($cfg.slack).botToken (not ($cfg.slack).existingSecret) }}
slack-bot-token: {{ $cfg.slack.botToken | b64enc | quote }}
{{- end }}
{{- if and ($cfg.slack).enabled ($cfg.slack).appToken (not ($cfg.slack).existingSecret) }}
slack-app-token: {{ $cfg.slack.appToken | b64enc | quote }}
{{- end }}
templates/_helpers.tpl (addition)
{{/*
Return the secret name to use for Slack credentials.
If existingSecret is set, use it; otherwise fall back to the chart-managed agent secret.
*/}}
{{- define "openab.slackSecretName" -}}
{{- if .cfg.slack.existingSecret -}}
{{ .cfg.slack.existingSecret }}
{{- else -}}
{{ include "openab.agentFullname" (dict "ctx" .root "agent" .agent "cfg" .cfg) }}
{{- end -}}
{{- end -}}
templates/deployment.yaml (change)
Use the helper for the Slack secretKeyRef names:
{{- if ($cfg.slack).enabled }}
- name: SLACK_BOT_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openab.slackSecretName" (dict "root" $ "agent" $name "cfg" $cfg) }}
key: slack-bot-token
- name: SLACK_APP_TOKEN
valueFrom:
secretKeyRef:
name: {{ include "openab.slackSecretName" (dict "root" $ "agent" $name "cfg" $cfg) }}
key: slack-app-token
{{- end }}
Expected secret key names
The existing secret must contain:
| Key |
Value |
slack-bot-token |
Bot User OAuth Token (xoxb-...) |
slack-app-token |
App-Level Token (xapp-...) |
These match the keys the chart already manages internally today — only the source of the Secret changes.
Behavior by agent configuration
| Agent uses |
existingSecret set? |
Result |
| Slack only |
No |
Chart creates agent Secret with slack-bot-token, slack-app-token (current behavior) |
| Slack only |
Yes |
No chart-managed Secret created; deployment references existingSecret directly |
| Slack + Discord |
No |
Chart creates agent Secret with all tokens (current behavior) |
| Slack + Discord |
Yes |
Chart creates agent Secret with discord-bot-token only; deployment references existingSecret for Slack keys |
The dual-secret case (Slack + Discord with existingSecret) is intentional: each token type can be managed independently.
Adaptation from openab-telegram
The openab-telegram chart (PR #873) implements existingSecret at chart level (single-agent chart):
# openab-telegram: chart-level, single agent
existingSecret: "my-bot-creds"
The openab chart is multi-agent, so the field is scoped per-agent:
# openab: per-agent, consistent with other per-agent fields
agents:
claude:
slack:
existingSecret: "my-bot-creds"
The underlying mechanism is identical: when existingSecret is set, the chart skips creating the Secret and references the named Secret in secretKeyRef.
Backwards Compatibility
existingSecret defaults to "" — no change in behavior for any existing deployment.
botToken and appToken fields remain; they are used when existingSecret is unset.
- No breaking change.
Validation
When existingSecret is set:
botToken and appToken are ignored (no validation required).
- Optionally emit a Helm note if both
existingSecret and botToken/appToken are non-empty simultaneously, to surface potential misconfiguration.
Testing
Summary
Add
agents.<name>.slack.existingSecretto theopenabchart. When set, the chart references the named Kubernetes Secret forSLACK_BOT_TOKENandSLACK_APP_TOKENinstead of creating a chart-managed Secret from Helm values.This adapts the
existingSecretpattern introduced in theopenab-telegramchart (PR #873) to the multi-agent structure of theopenabchart — scoped per-agent rather than chart-level.Motivation
GitOps / External Secrets Operator deployments. In GitOps environments, secrets are typically managed by an operator such as External Secrets Operator (ESO) that syncs values from an external store (AWS Secrets Manager, Vault, etc.) into Kubernetes Secrets on a refresh interval.
The
openabchart already supports this pattern forANTHROPIC_API_KEYviasecretEnv(rendered asvalueFrom.secretKeyRef). Slack tokens currently cannot follow the same path because the chart creates its own Secret from Helm values, requiring them to be passed athelm install/helm upgradetime:This means every token rotation requires a Helm re-apply, blocking automation and making platform engineers a bottleneck for what should be a self-service operation.
With
existingSecret, all credentials follow the same ESO path:When the external store is updated, ESO syncs the Kubernetes Secret within its refresh interval. The pod picks up the new tokens on its next restart — no Helm re-apply, no manual intervention.
Self-Service Bot Platform Context
We deploy OpenAB as a multi-tenant platform where each team owns a skill repo — a Git repository containing a
SKILL.mdthat defines the bot's behaviour. When a team merges a change toSKILL.md, a CI job automatically restarts the pod (~90 seconds end-to-end). Teams iterate on bot behaviour with zero platform involvement.The infrastructure workload (namespace, ServiceAccount, ESO ExternalSecret, Helm release) is provisioned once via a standard Terraform module. After that initial setup, the platform team should never need to touch it again — but today they must re-apply whenever Slack tokens rotate, because of
helm_release_set_sensitive.existingSecretcloses this last gap:Proposed Change
values.yamltemplates/secret.yaml(change)The current
$hasSlackvariable is:When
existingSecretis set and bothbotToken/appTokenare left empty,$hasSlackis alreadyfalse— for Slack-only agents, the chart-managed Secret is not created at all. For agents that also use Discord, STT, or gateway, the chart-managed Secret is still created for those tokens; only the Slack entries are suppressed.Guard the Slack entries explicitly:
templates/_helpers.tpl(addition)templates/deployment.yaml(change)Use the helper for the Slack
secretKeyRefnames:Expected secret key names
The existing secret must contain:
slack-bot-tokenxoxb-...)slack-app-tokenxapp-...)These match the keys the chart already manages internally today — only the source of the Secret changes.
Behavior by agent configuration
existingSecretset?slack-bot-token,slack-app-token(current behavior)existingSecretdirectlydiscord-bot-tokenonly; deployment referencesexistingSecretfor Slack keysThe dual-secret case (Slack + Discord with
existingSecret) is intentional: each token type can be managed independently.Adaptation from
openab-telegramThe
openab-telegramchart (PR #873) implementsexistingSecretat chart level (single-agent chart):The
openabchart is multi-agent, so the field is scoped per-agent:The underlying mechanism is identical: when
existingSecretis set, the chart skips creating the Secret and references the named Secret insecretKeyRef.Backwards Compatibility
existingSecretdefaults to""— no change in behavior for any existing deployment.botTokenandappTokenfields remain; they are used whenexistingSecretis unset.Validation
When
existingSecretis set:botTokenandappTokenare ignored (no validation required).existingSecretandbotToken/appTokenare non-empty simultaneously, to surface potential misconfiguration.Testing
existingSecretunset — existing behavior unchanged (chart creates Secret from values)existingSecretset, Slack-only agent — no chart-managed Secret created; bot connects using tokens from external secretexistingSecretset, Slack + Discord agent — chart-managed Secret contains onlydiscord-bot-token; Slack env vars reference external secretexistingSecretset, referenced secret missing — pod fails with clearsecretKeyReferror (expected Kubernetes behavior)