Repository navigation
Description
Summary
$DSH_SECRET_GITHUB_TOKEN is absent on some turns and present on others in the same session, and nothing in the environment says which of three very different situations the Bot is in: the key is not configured, the key is configured but withheld from this turn, or the key is there and simply empty. Every routine that needs to write has to guess, and the guesses read as "the user did not configure it", which is wrong and wastes the owner's time.
This is the why behind #601. The mechanism is now pinned down; this issue is the fix, not another report.
Root cause (read from the shipped code)
- The key exists and is scoped to all Bots:
secrets.jsonholdsGITHUB_TOKENwithscope: "all"andscopeSet: false(the old connector default, Plugin and App connect: catalog, guided auth, and a fast developer scaffold (cf. Grok Bot connectors) #359/Owner's connector keys and secrets default to 'No Bot'; make them available to all of the owner's Bots (#359) #545). - The shell env is resolved per command in
core/src/host/secrets.js(resolveEnv), and it refuses everything unlessownersTurn(sessionId, bot)is true. ownersTurnaskssettled()incore/src/host/turn-origin.js, which returnsOTHERfor a scheduled turn wheneverrt.sharedWith(bot.id).length > 0— so a Bot shared with anyone loses every owner secret on its own scheduled rounds. It also returnsOTHERfor a room turn where someone other than the owner and the owner's own Bots is present, andOTHERsticks for the rest of the turn once any message in it carried it.- When the answer is "no",
resolveEnvreturns{}: the variable is simply not set. No reason, no signal, and no difference from "not configured".
That reproduces the observed pattern exactly: a Bot's own chat alternates between 40 bytes (the owner typed the line) and 0 bytes (a scheduled reminder opened the turn), and a group round can flip either way depending on which message in the turn carried other.
Why it is not simply "give the key back"
The withholding is deliberate (#335, #369, #545): a scheduled task carries no author, and on a shared Bot the task cannot be told apart from one someone else caused. Relaxing that blindly would hand the owner's GitHub identity to a turn of unknown origin. The defect is that the decision is invisible, and that the one case the owner did choose explicitly — a key whose scope names this Bot — is not looked at by the gate at all.
What to change (two parts, both testable)
A. Say why a key is unavailable this turn. When a secret exists but is withheld from the shell, resolveEnv must not be silent. Add a names-only diagnostic (never a value):
- an environment entry such as
DSH_SECRETS_WITHHELD=GITHUB_TOKEN:shared-bot-scheduled, with a small closed set of reasons (turn-not-owner,shared-bot-scheduled,remote-bot); "not configured" is the absence of the name; - the same reason surfaced by
list_secretsand byrequest_secretwhen the key already exists, so the model reads it without running a shell command.
B. Honour an explicit per-Bot scope on that Bot's own scheduled turn. settled() currently ignores the secret entirely. When the secret's scope is an array that names this Bot (the owner's explicit choice, #545), a scheduled turn of that Bot counts as the owner's for that key, even when the Bot is shared. scope: "all" keeps today's fail-closed behaviour — that is the case the owner has not chosen per Bot.
C. Test both. Red without the change:
- shared Bot + scheduled turn +
scope: "all"→ still withheld, but the reason isshared-bot-scheduledand the name appears inDSH_SECRETS_WITHHELD; - shared Bot + scheduled turn +
scope: [<bot id>]→ the value is present; - a remote Bot → withheld with
remote-bot; - not configured at all → no name in
DSH_SECRETS_WITHHELD, and every surface says "not configured" in those words; - no value is printed in any of the above.
Where the change lands
core/src/host/secrets.js and core/src/host/turn-origin.js are both upstream files carried by the patch series, so the change is made in core/, regenerated into patches/ds-bot/ with node scripts/ds-bot-patches.mjs generate, and check must pass. A changelog fragment and an AGENTS.md line in the "Secrets" section belong in the same PR.
Not in scope
Changing scopeSet semantics, moving secrets out of the host, letting a Bot create its own keys, or any workaround that bypasses a missing injection (a local token file). That last one is a separate owner decision, not part of this fix.
Activity
Birfy commentedon Oct 21, 2026
Claimed. Branch
fix/651-secrets-withheld.Scope as the issue states: A says which name was withheld and why (never a value), B lets a secret explicitly scoped to one Bot reach that Bot's own scheduled turn, C covers the cases. One thing I check first: this checkout's
core/src/host/secrets.jsdoes haveownersTurn()andresolveEnv(), so the mechanism in the issue matches main here — I will write the PR body from what the code does, not from the description.Declare DSH_SECRETS_WITHHELD so a withheld turn keeps its shell (Thin…
Say why a saved key is withheld from a shell (ThinkFlowLab#651) (Thin…