Skip to content

Secrets: an absent $DSH_SECRET_* says nothing — distinguish not-configured, withheld-this-turn and empty, and honour an explicit per-Bot scope on the owner s scheduled turns #651

Closed

Description

@Birfy

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)

  1. The key exists and is scoped to all Bots: secrets.json holds GITHUB_TOKEN with scope: "all" and scopeSet: 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).
  2. The shell env is resolved per command in core/src/host/secrets.js (resolveEnv), and it refuses everything unless ownersTurn(sessionId, bot) is true.
  3. ownersTurn asks settled() in core/src/host/turn-origin.js, which returns OTHER for a scheduled turn whenever rt.sharedWith(bot.id).length > 0 — so a Bot shared with anyone loses every owner secret on its own scheduled rounds. It also returns OTHER for a room turn where someone other than the owner and the owner's own Bots is present, and OTHER sticks for the rest of the turn once any message in it carried it.
  4. When the answer is "no", resolveEnv returns {}: 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_secrets and by request_secret when 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 is shared-bot-scheduled and the name appears in DSH_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

  1. added
    bugSomething isn't working
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    area:automationScheduled tasks and automation
    area:securityAuth, grants, approvals, secrets
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 21, 2026
  2. self-assigned this
    on Oct 21, 2026
  3. Birfy commented on Oct 21, 2026

    @Birfy
    CollaboratorAuthor

    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.js does have ownersTurn() and resolveEnv(), so the mechanism in the issue matches main here — I will write the PR body from what the code does, not from the description.

  4. removed
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 21, 2026
  5. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:automationScheduled tasks and automationarea:securityAuth, grants, approvals, secretsbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions