Skip to content

fix(telegram): resolve env SecretRef bot tokens before startup#56784

Closed
davetanaka wants to merge 1 commit intoopenclaw:mainfrom
davetanaka:codex/telegram-secretref-fix
Closed

fix(telegram): resolve env SecretRef bot tokens before startup#56784
davetanaka wants to merge 1 commit intoopenclaw:mainfrom
davetanaka:codex/telegram-secretref-fix

Conversation

@davetanaka
Copy link
Copy Markdown

OpenClaw Upstream PR Draft - Telegram SecretRef

Why this PR matters

This is a small but meaningful community contribution.

  • It fixes a real runtime failure
  • It improves SecretRef-first deployments
  • It helps users who avoid plaintext secrets in env/service configs
  • It adds regression coverage

Recommended PR title

fix(telegram): resolve env SecretRef bot tokens before provider startup

Alternative:

fix(telegram): support SecretRef-style TELEGRAM_BOT_TOKEN during monitor startup

Suggested PR body

## Summary

This fixes a Telegram startup failure when `TELEGRAM_BOT_TOKEN` is provided as a SecretRef-style env value (for example `op://...`) instead of a plaintext token.

Before this change, the Telegram monitor path could treat the unresolved env string as the actual bot token and pass it directly into Telegram API calls. In practice that caused startup failures such as:

- `deleteWebhook failed`
- `deleteMyCommands failed`
- `setMyCommands failed`
- `404 Not Found`

## Root cause

`monitorTelegramProvider` relied on the env/config-resolved token path, but if the token source was `env`, the runtime could still use `process.env.TELEGRAM_BOT_TOKEN` as-is during startup.

That works for plaintext env tokens, but not for SecretRef-style values.

## What this changes

- adds a SecretRef-aware token resolution step before Telegram provider startup
- resolves explicit/env token inputs through the configured secret-input runtime path
- fails explicitly when the SecretRef is unavailable instead of passing `op://...` to Telegram
- keeps existing plaintext-token behavior unchanged

## Test coverage

Adds a regression test covering the case where:

- `TELEGRAM_BOT_TOKEN` is set to a SecretRef-style value
- runtime secret resolution returns a plaintext token
- Telegram monitor startup uses the resolved plaintext token

## Why this matters

This helps OpenClaw behave more consistently in SecretRef-first setups, especially for service/daemon environments where users want to avoid plaintext secrets in `.env`, launchd, or systemd configuration.

Short issue-style summary

If you want a short companion issue or note first:

Telegram startup can fail when `TELEGRAM_BOT_TOKEN` is supplied as a SecretRef-style env value (for example `op://...`).

The bot token itself may still be valid, but the runtime path can pass the unresolved env string into Telegram API calls, producing `deleteWebhook` / command-sync failures with `404 Not Found`.

I have a small fix + regression test prepared and can open a PR.

Suggested submission flow

  1. Create a branch in the upstream clone
  2. Commit only the 2 Telegram source files
  3. Open a small PR with the body above
  4. If maintainers want more context, add the reproduction notes in a comment

Scope to include

Include:

  • extensions/telegram/src/monitor.ts
  • extensions/telegram/src/monitor.test.ts

Do not include:

  • local service plist edits
  • local .env edits
  • local dist/ patching for live recovery
  • workspace / personal runbook files

Confidence note

Good to mention if asked:

  • targeted Telegram monitor test passes
  • root cause was locally reproduced and traced
  • the fix is intentionally narrow

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: S labels Mar 29, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee8834f115

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +107 to +111
const resolved = await resolveConfiguredSecretInputString({
config: params.cfg,
env: process.env,
value: envToken,
path: "TELEGRAM_BOT_TOKEN",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve non-template SecretRef env tokens before returning

This branch still passes raw TELEGRAM_BOT_TOKEN values through when they are not parsed as a built-in SecretRef shape, so strings like op://... are returned unchanged and used as the bot token. That means the startup failure this change targets can still occur in production even though the new test passes, because the test mocks resolveConfiguredSecretInputString to return a resolved token for that input instead of exercising real parsing behavior.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 29, 2026

Greptile Summary

This PR adds a resolveMonitorTelegramToken helper that passes bot tokens through resolveConfiguredSecretInputString before the Telegram provider starts, fixing startup failures when TELEGRAM_BOT_TOKEN is a SecretRef-style value (e.g. op://...). The env-token path and accompanying regression test work correctly for the primary use case.

Issues found:

  • Explicit SecretRef fallback logic is broken (extensions/telegram/src/monitor.ts lines 96–101): when opts.token is a SecretRef that cannot be resolved, the guard explicitToken === params.resolvedAccountToken is almost always false (the explicit token and the account-resolved token come from different sources), so the throw is skipped and the unresolved op://... string is returned to the caller. This passes the raw SecretRef to Telegram API calls — the exact failure mode the PR is intended to prevent — in the explicit-token code path.

  • Env stub leaks across tests (extensions/telegram/src/monitor.test.ts): vi.stubEnv(\"TELEGRAM_BOT_TOKEN\", ...) is called in the new test but vi.unstubAllEnvs() is never called in afterEach. This violates the repo's testing guideline requiring cleanup of env/globals/state so --isolate=false stays green.

Confidence Score: 3/5

Not safe to merge without addressing the explicit SecretRef fallback — the primary new code path silently passes unresolved SecretRef strings to Telegram in a common scenario.

A P1 logic defect exists in the explicit-token branch of resolveMonitorTelegramToken: the equality check used to decide whether to throw (explicitToken === params.resolvedAccountToken) is almost never true when an explicit SecretRef is passed via opts.token, so the function silently returns the unresolved op://... string. This directly undermines the PR's stated goal. Additionally there is a P2 env-cleanup gap in the test.

extensions/telegram/src/monitor.ts (explicit-token SecretRef fallback logic, lines 96–101) and extensions/telegram/src/monitor.test.ts (missing vi.unstubAllEnvs() in afterEach).

Important Files Changed

Filename Overview
extensions/telegram/src/monitor.ts Adds resolveMonitorTelegramToken helper to resolve SecretRef-style bot tokens before startup. The env-token path (source=env) is correct; the explicit-token path has a logic flaw where a failed SecretRef resolution falls back to returning the unresolved string instead of throwing.
extensions/telegram/src/monitor.test.ts Adds mock wiring and regression test for env SecretRef resolution. The test correctly covers the primary use case but uses vi.stubEnv without a corresponding vi.unstubAllEnvs() in afterEach, leaking the env stub to subsequent tests.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/telegram/src/monitor.ts
Line: 96-101

Comment:
**Unresolved explicit SecretRef silently passed to Telegram**

When `opts.token` is a SecretRef string (e.g. `op://vault/telegram`) and `resolveConfiguredSecretInputString` cannot resolve it, the code reaches line 101 only if `explicitToken !== params.resolvedAccountToken`. In practice this condition is almost always false — the explicit token passed via `opts.token` and `account.token` typically come from different sources — so the `throw` on line 97 is never reached for a failed explicit SecretRef. The result is that `return explicitToken` silently returns the unresolved `op://...` string as the bot token, causing the same `404 Not Found` / `deleteWebhook failed` failures this PR claims to prevent.

Consider throwing unconditionally when `resolved.value` is falsy for the explicit-token path, instead of relying on the `explicitToken === resolvedAccountToken` equality check:

```suggestion
    if (resolved.value) {
      return resolved.value;
    }
    throw new Error(
      `Telegram bot token unavailable: ${resolved.unresolvedRefReason ?? "explicit token SecretRef is unresolved."}`,
    );
  }
```

If you intentionally want to allow plaintext fallback for non-SecretRef values, `resolveConfiguredSecretInputString` should return the original plaintext as `resolved.value` for non-SecretRef inputs, making the fallback unreachable for valid tokens.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/telegram/src/monitor.test.ts
Line: 424

Comment:
**`vi.stubEnv` not cleaned up — env leaks to subsequent tests**

`vi.stubEnv("TELEGRAM_BOT_TOKEN", ...)` stubs the env variable for the remainder of the test suite unless `vi.unstubAllEnvs()` is called. The current `afterEach` block only calls `consoleErrorSpy?.mockRestore()`, so `TELEGRAM_BOT_TOKEN` stays set to `"op://secrets/telegram/credential"` for every test that runs after this one.

The repo testing guideline explicitly requires: _"Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so `--isolate=false` stays green._"

Add `vi.unstubAllEnvs()` to the existing `afterEach`:

```ts
afterEach(() => {
  consoleErrorSpy?.mockRestore();
  vi.unstubAllEnvs();
});
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(telegram): resolve env SecretRef bot..." | Re-trigger Greptile

Comment on lines +96 to +101
if (explicitToken === params.resolvedAccountToken) {
throw new Error(
`Telegram bot token unavailable: ${resolved.unresolvedRefReason ?? "explicit token SecretRef is unresolved."}`,
);
}
return explicitToken;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unresolved explicit SecretRef silently passed to Telegram

When opts.token is a SecretRef string (e.g. op://vault/telegram) and resolveConfiguredSecretInputString cannot resolve it, the code reaches line 101 only if explicitToken !== params.resolvedAccountToken. In practice this condition is almost always false — the explicit token passed via opts.token and account.token typically come from different sources — so the throw on line 97 is never reached for a failed explicit SecretRef. The result is that return explicitToken silently returns the unresolved op://... string as the bot token, causing the same 404 Not Found / deleteWebhook failed failures this PR claims to prevent.

Consider throwing unconditionally when resolved.value is falsy for the explicit-token path, instead of relying on the explicitToken === resolvedAccountToken equality check:

Suggested change
if (explicitToken === params.resolvedAccountToken) {
throw new Error(
`Telegram bot token unavailable: ${resolved.unresolvedRefReason ?? "explicit token SecretRef is unresolved."}`,
);
}
return explicitToken;
if (resolved.value) {
return resolved.value;
}
throw new Error(
`Telegram bot token unavailable: ${resolved.unresolvedRefReason ?? "explicit token SecretRef is unresolved."}`,
);
}

If you intentionally want to allow plaintext fallback for non-SecretRef values, resolveConfiguredSecretInputString should return the original plaintext as resolved.value for non-SecretRef inputs, making the fallback unreachable for valid tokens.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/telegram/src/monitor.ts
Line: 96-101

Comment:
**Unresolved explicit SecretRef silently passed to Telegram**

When `opts.token` is a SecretRef string (e.g. `op://vault/telegram`) and `resolveConfiguredSecretInputString` cannot resolve it, the code reaches line 101 only if `explicitToken !== params.resolvedAccountToken`. In practice this condition is almost always false — the explicit token passed via `opts.token` and `account.token` typically come from different sources — so the `throw` on line 97 is never reached for a failed explicit SecretRef. The result is that `return explicitToken` silently returns the unresolved `op://...` string as the bot token, causing the same `404 Not Found` / `deleteWebhook failed` failures this PR claims to prevent.

Consider throwing unconditionally when `resolved.value` is falsy for the explicit-token path, instead of relying on the `explicitToken === resolvedAccountToken` equality check:

```suggestion
    if (resolved.value) {
      return resolved.value;
    }
    throw new Error(
      `Telegram bot token unavailable: ${resolved.unresolvedRefReason ?? "explicit token SecretRef is unresolved."}`,
    );
  }
```

If you intentionally want to allow plaintext fallback for non-SecretRef values, `resolveConfiguredSecretInputString` should return the original plaintext as `resolved.value` for non-SecretRef inputs, making the fallback unreachable for valid tokens.

How can I resolve this? If you propose a fix, please make it concise.

});

it("resolves env SecretRef token before starting the provider", async () => {
vi.stubEnv("TELEGRAM_BOT_TOKEN", "op://secrets/telegram/credential");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 vi.stubEnv not cleaned up — env leaks to subsequent tests

vi.stubEnv("TELEGRAM_BOT_TOKEN", ...) stubs the env variable for the remainder of the test suite unless vi.unstubAllEnvs() is called. The current afterEach block only calls consoleErrorSpy?.mockRestore(), so TELEGRAM_BOT_TOKEN stays set to "op://secrets/telegram/credential" for every test that runs after this one.

The repo testing guideline explicitly requires: "Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so --isolate=false stays green."

Add vi.unstubAllEnvs() to the existing afterEach:

afterEach(() => {
  consoleErrorSpy?.mockRestore();
  vi.unstubAllEnvs();
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/telegram/src/monitor.test.ts
Line: 424

Comment:
**`vi.stubEnv` not cleaned up — env leaks to subsequent tests**

`vi.stubEnv("TELEGRAM_BOT_TOKEN", ...)` stubs the env variable for the remainder of the test suite unless `vi.unstubAllEnvs()` is called. The current `afterEach` block only calls `consoleErrorSpy?.mockRestore()`, so `TELEGRAM_BOT_TOKEN` stays set to `"op://secrets/telegram/credential"` for every test that runs after this one.

The repo testing guideline explicitly requires: _"Write tests to clean up timers, env, globals, mocks, sockets, temp dirs, and module state so `--isolate=false` stays green._"

Add `vi.unstubAllEnvs()` to the existing `afterEach`:

```ts
afterEach(() => {
  consoleErrorSpy?.mockRestore();
  vi.unstubAllEnvs();
});
```

How can I resolve this? If you propose a fix, please make it concise.

@wzfukui
Copy link
Copy Markdown
Contributor

wzfukui commented Apr 6, 2026

This PR was green on March 29, but main has moved since then and the branch now needs an update/rebase before merge.

@joshavant
Copy link
Copy Markdown
Contributor

Thanks for this Telegram SecretRef fix proposal, @davetanaka.

Outcome: this PR is superseded by #66818, which landed a broader version of the same runtime fix.

Why #66818 covers this: Telegram token resolution now supports env-backed SecretRefs at runtime, preserves strict behavior for unresolved non-env refs, and enforces env provider policy checks.

@joshavant joshavant closed this Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants