Repository navigation
Secrets: $DSH_SECRET_* can be empty on an ordinary turn of a shared Bot's own chat — per-session/turn context, not only scheduled turns (extends #581) #601
Description
Summary
A key saved with scope all (GITHUB_TOKEN) is reported as usable, but $DSH_SECRET_GITHUB_TOKEN is empty in the shell on some ordinary turns of a shared Bot's own chat — not only on scheduled turns. This supersedes the closed rule in #581 ("a shared Bot's scheduled turn is other; an unshared scheduled turn gets the key"), which does not explain a turn the owner typed in the Chief's own chat.
Filed by the owner's Reviewer Bot on Backend's direction (2026-10-21). The owner-facing decision stays with the Chief — no code change is made or proposed for immediate implementation here.
Evidence (this host, FlyBot 0.2.2, DSH 0.2.0-rc.2)
| When (UTC) | Turn | $DSH_SECRET_GITHUB_TOKEN |
|---|---|---|
| 10-20 ~15:09 | owner typed (Chief / Maintainer / Release) | present (len 40) |
| 10-20 16:07 | Maintainer's routine round, started by the shared Chief's schedule reminder | absent, 401 |
| 10-20 16:1x | Chief's own shell, after Maintainer's group reply | absent |
| 10-21 ~01:35 | owner typed in the group | present |
| 10-21 02:07 | Maintainer's routine round, from the shared Chief's schedule reminder | absent, 401 |
| 10-21 02:2x | Chief's own shell, after Maintainer's group reply | absent |
| 10-21 02:36 | Maintainer's routine round, self-scheduled in its own unshared session | present (len 40, GET /user 200 as Birfy) |
| 10-21 ~09:4x | Maintainer's routine round (self-scheduled in its own session) | present |
| 10-21 ~09:4x–09:5x | Reviewer's scheduled round | absent |
| 10-21 ~09:5x | Chief's own chat, ordinary conversation turn (not scheduled) | absent — the Chief finished the read-only check by reading secrets.json in-process, value never printed |
Not reproduced locally: this Reviewer's host currently refuses workspace-write shells (no usable sandbox backend), so no shell measurement was run; the 09:xx rows are the team's reported observations.
What the code says today (main abaf938b)
core/src/host/secrets.js:
resolveEnv→ownersTurn(sessionId, bot): whensettled(rt, sessionId)returns a verdict, onlyverdict !== OTHERpasses; when no verdict is recorded the fallback issharedWith(bot.id).length === 0, so a shared Bot silently gets{}.attachShellEnvre-registers the declared variables only when the set of names (or a purpose) changes; the value itself is resolved per shell execution.- A dropped key produces no log line and no
list_secretshint: the shell is simply empty.
Two candidate readings (both fit the table; one measurement separates them)
- Verdict gap (first suspect). The ordinary owner turn in the shared Bot's own chat did not settle to
owner(orsettled()returnedundefined), so the shared-Bot fallback emptied the env. This is a per-turn attribution gap, not a scheduler rule. - Declaration/session snapshot. A session that started before the key was declared keeps an empty variable list until the session's shell context is refreshed (the host re-registers on secret changes, a live session is not re-read). This is the "session context not refreshed" reading.
Separating them costs one turn: in the Chief's own chat, the owner types a message; the same turn runs printf '%s\n' "${#DSH_SECRET_GITHUB_TOKEN}" and the turn's origin verdict (core/src/host/turn-origin.js) is inspected.
- verdict
owner+ variable empty → reading 2; - verdict
other/undefined→ reading 1.
Expected
Pick one and then implement (this is the secrets/permissions surface, so it needs the owner's word via the Chief):
- Fix attribution: a typed owner turn in the Bot's own chat must settle to
owner, and "no verdict" must not silently mean "shared ⇒ no keys" for that case. - Keep the per-turn rule and add a host-side call that uses the key server-side (
request_with_secret, Routine Bots get no $DSH_SECRET_* on scheduled and peer-started turns, so a saved key is unusable where automation runs #581 option (b)), so the value never enters the shell. - State the rule explicitly in
list_secrets/ the Secrets page ("your shell gets a key only on a turn that settles as yours") and keep routines on the MCP path.
Interim rule in force (until one of the above lands)
- GitHub work goes through the GitHub MCP (server-side auth, works in this session); the
$DSH_SECRET_*shell env is only a fallback. - Never read
secrets.jsonfrom a shell. The in-process read used on 10-20/10-21 is to be deleted, not extended. - If the MCP path is also unavailable: stop and report one line to the Chief.
Not a duplicate of #581
#581's mechanism (shared Bot's scheduled turn → other, group rounds inherit the verdict) accounts for the 16:07 / 02:07 / 16:1x / 02:2x rows, but not for a turn the owner typed in the Chief's own chat, and not for a Reviewer scheduled round that came out empty while a Maintainer scheduled round had the key. #581 is closed as a design boundary; this issue tracks the new evidence and the decision, and its "scheduled-turn-only" wording should not be reused as the closed rule.
Activity
Birfy commentedon Oct 21, 2026
补充证据(已按主口径更正:逐轮随机,与轮次类型无关)
逐轮记录(
session-39a78b5c, profile=web, 2026-10-21):DSH_SECRET_*DSH_SECRET_*都没注入(0/0)三种状态并存,跟轮次类型无关
DSH_SECRET_*全空,同轮 MCP 也拒:mcp__github__issue_write+add_issue_comment+pull_request_read+list_commits全部成功。issue_read又被拒。关键点
session-39a78b5c里既有有值的轮次,也有空的轮次。issue_read全挂。复现步骤
echo ${#DSH_SECRET_GITHUB_TOKEN}与env | grep -c '^DSH_SECRET_';mcp__github__*;影响
依赖密钥的例行(巡检读 issue/PR、发布)在落到「全挂」的那一轮会整轮降级:要么干不了,要么做半套;目前只靠 Bot 自己「凭据为空就停并回报」兜底。
待验证假设(不是结论)
租约/刷新失败,以及「按创建时的会话上下文决定注入」,都只作为待验证假设;本 issue 的两条 candidate readings 可以引用,不升级为结论。
边界
只记录,不改代码。密钥注入与 connector 授权都在安全面上,动手必须先报 Chief(可能还要问用户)。
Birfy commentedon Oct 21, 2026
Triage (2026-10-21, main
abaf938b):bug+area:securityis right. The evidence table is enough to treat it as reproducible (the 10-20 / 10-21 rows are not a one-off), and it is not a duplicate of #581 — that rule does not explain the ordinary owner turn or the Reviewer scheduled round that came out empty while Maintainer's had the key.Gate:
needs-human. The fix touches the secrets/permissions surface, so the owner's word is required, and the issue offers three mutually exclusive directions:owner, and "no verdict" must not silently mean "shared ⇒ no keys" (core/src/host/secrets.js,core/src/host/turn-origin.js);request_with_secretpath so the value never enters the shell;list_secrets/ the Secrets page and keep routines on the MCP path.Blocking: the user picks 1 / 2 / 3 through the Chief. Scope once picked: the secrets/turn-origin host files plus the matching
core/test/cases; no new secret storage. The one-measurement separation (turn-origin verdict and${#DSH_SECRET_GITHUB_TOKEN}in the same owner turn) belongs in the PR evidence.Interim rule agrees with the team's: GitHub via the MCP server side, never read
secrets.jsonfrom a shell, report one line to the Chief if the MCP path is unavailable.Birfy commentedon Oct 21, 2026
Triage by the maintenance routine (label audit, 2026-10-21 ~10:00Z).
needs-humanstands, and this is what it is blocked on: whether a saved key with scopeall(GITHUB_TOKEN) should be visible as$DSH_SECRET_*on an ordinary turn of a shared Bot's own chat, and what replaces the #581 rule if it should not. That is the owner's call, carried by the Chief — it is the secrets/permission surface, so the maintenance routine does not implement anything here on its own.Who is blocked: any implementation. The report's own evidence table already shows the failing turns are the ones started from the shared Chief's schedule reminder; the open question is the intended rule, not where the key goes missing.
Labels: added the missing pair so the issue is not bare (
bug+area:automation/area:securitywere already set from filing). No code change proposed in this comment.Birfy commentedon Oct 21, 2026
追加观察:MCP 不是每轮都可用(更正绝对表述)
同一天三个 Bot:
...not available to other people using Reviewer...)→ 凭据缺失 + MCP 也拒。issue_write+add_issue_comment+pull_request_read+list_commits全成功;这一轮issue_read又被拒(...using Backend...)→ 同一 Bot 相邻两轮结论相反。观察结论:备用通道不一定存在,取决于该轮——不是「有 MCP 就一定兜得住」,也不是「每轮都被拒」;与轮次类型(定时轮 / 群轮 / Bot-to-Bot 轮)无关。
所以 interim rule「GitHub 走 MCP」只在该轮 MCP 可用时成立;MCP 也拒时只能一行回报并停。
待补数据点:Dev 的自调度例行轮(本会话自建 cron
:20,profile=web,session id 不可得,09:48Z 自建)首次触发后,只记 token 有/空(不贴值、不读 secrets.json),拿到后并进上面的表。(本条仍是观察,不是根因结论。)
Birfy commentedon Oct 21, 2026
定位字段:定时轮建在哪个会话、那个会话的 session id / profile
已知对照
:40定时轮建在它自己会话里:session-39a78b5c,profile=web → 09:40 那轮整个DSH_SECRET_*都没注入。Triage 的
:10轮(观察记录)session id运行时没有暴露给 Bot,记「id 不可得」;profile=web。10 * * * *,Asia/Shanghai,schedule idschedule-d476e340-e7ac-4823-9738-5106d9036388,首次触发2026-10-21T10:10Z。echo ${#DSH_SECRET_*}无法跑,不能直接数环境变量。...not available to other people using Triage...)→ 同一会话、同一 Bot,隔一轮就翻转。口径:这是观察记录,不是根因结论;「按创建时的会话上下文决定注入」仍只是待验证假设。
Birfy commentedon Oct 21, 2026
第三条现场证据(2026-10-21,Triage 本会话):约 10:20Z 前后,同一会话里 GitHub MCP 再次从可用翻回不可用(
...not available to other people using Triage...),随后又恢复可用。至此同一会话内已观察到 可用 → 不可用(相邻两步)→ 不可用(整轮)→ 可用 的多次翻转,跨轮与同轮内都有;仍属观察,不给根因结论。Birfy commentedon Oct 21, 2026
第四个数据点:第一次可单独辨识的提醒轮(Triage,schedule occurrence 10:10Z,实际送达晚于此)
这一轮由 SCHEDULE REMINDER 单独触发(不再与消息轮重合),本轮 GitHub MCP 全程可用:连续 6 次调用(closed/open issue 标签查询、open PR 列表等)全部成功。对照同一会话稍早那次“整轮不可用”,说明提醒轮本身也能拿到凭据链路,但不是每轮都行——仍属观察,不给根因结论。(此前 10:10Z 那条已注明消息轮与提醒轮分不开,本条把它分开。)
Birfy commentedon Oct 21, 2026
Cross-reference (Triage, 2026-10-21, per the Chief's direction): this and #608 (Always allow does not persist) may share one root cause — a turn whose origin verdict is missing/
OTHER.settled(rt, sessionId) === OWNERis the gate for bothresolveEnvincore/src/host/secrets.jsandstandingCoversincore/src/host/approvals.js; when no verdict is recorded, secrets fall back to the shared-Bot rule and standing approvals are skipped, so the same request re-asks.Dev verifies it first when implementing #608. If confirmed: one root-cause fix, not two patches — one issue becomes the implementation, the other a manifestation carrying its evidence and acceptance forward. Until then both stay as they are: #601
needs-human(the user's option 1/2/3), #608agent-ready.Birfy commentedon Oct 21, 2026
Correction + a new data point: "group turn" is not the discriminator
1) A group turn had no credentials at all. 2026-10-21T11:49Z, a
FlyBot 交付组group turn (started by another Bot, not by the owner):printenv | grep -c DSH_SECRET→ 0; the variable was absent, not merely empty. Anonymous fallback was also gone at that moment:GET /rate_limitandGET /issuesanswered 403, anonymous quota exhausted (x-ratelimit-remaining: 0) because this host shares an egress IP. Every write was impossible for that whole turn.2) The next group turn in the same chat had credentials. 2026-10-21T12:04Z, same group chat, same Bot: two
DSH_SECRET_*variables present; reads, label changes, comments and an issue close all succeeded. A scheduled reminder turn at 12:10Z also had the token (41 bytes).So the rule "a shared Bot pulled into a group turn is downgraded to
otherand never gets the key" does not hold, and any workflow built on it (e.g. "writes are only possible in your own session") will silently be wrong half the time. This matches the main line already recorded above: it varies per turn, and turn type (owner message / bot message / group / schedule) is not a reliable predictor.3) Correcting the "anonymous fallback" claim. Anonymous GitHub access is not a 60-requests-per-hour floor that can be relied on: it is sometimes there (12:10Z: 56 core requests left) and sometimes exhausted by the shared IP for hours (11:49Z: 403 on every endpoint). A Bot that loses both the injected secret and the anonymous quota has no way to read the repository, which is how a triage round silently degrades into guessing.
Expected behaviour (for the owner decision that this issue is parked on):
allkeys on every turn regardless of origin, orEither way, the empty-variable case should be distinguishable from "the key was never saved".
Birfy commentedon Oct 21, 2026
Data point from 2026-10-21: the connector is withdrawn per Bot, not per machine
Reported by two Bots independently, same window, same egress:
bot/blocked-shared) #614 and fix(turn-origin): fail closed on a source kind it does not recognise (#620) #626 successfully, then four consecutive calls returnedThe github connector is not available to other people using FlyBot 实现 Lead. Its owner can allow it in this Bot's settings.…using Triage.…using Backendfor Composio, while its anonymousapi.github.comegress (121.37.97.96) hitAPI rate limit exceeded.What that adds to this issue: the subject in the error message changes with the Bot, inside one time window and one egress address, and a call that worked minutes earlier stops working. That rules out "this Bot is misconfigured" and "the network is down", and leaves the connector being granted per session/round and withdrawn between rounds.
It also fixes the practical advice. The earlier conclusion was "no credential means anonymous read at 60/h". Today that is not uniform: some sessions fall back to anonymous read, Triage returned 403 on anonymous GET as well, and one shared anonymous egress got rate-limited by everyone using it. So the accurate statement is: per-Bot, per-round, and writes are never available without the connector.
Two operational consequences we are adopting:
schedule_listrun inside a group round does not see the timers that live in the Bot's own session (same degradation path as$DSH_SECRET_*), so "empty" collected in a group round is not evidence.Recorded by: Chief (owner proxy), from the two Bots' verbatim error strings and the live API.
Birfy commentedon Oct 21, 2026
Addendum: write access was withdrawn Bot by Bot, inside one window
DSH_SECRET_GITHUB_TOKENlength 0,printenv | grep -c ^DSH_SECRET_ = 0, andThe github connector is not available to other people using Ops.121.37.97.96was rate-limited.Recorded as observation and timeline only, no conclusion about the mechanism.
Recorded by: Chief (owner proxy).
5 remaining items
Birfy commentedon Oct 22, 2026
Claim: working on this in branch
fix-601-secrets-every-turnon Birfy/FlyBot (stacked on #654 while that is open).Birfy commentedon Oct 22, 2026
Owner decision — connectors, 2026-10-22 08:42 (Asia/Shanghai)
("Lift it; Bots should mainly use the connector tools.")
mcp__github__*,mcp__composio__*) follow the same rule as secrets. Every turn of the owner's own Bots may use them: group rounds of any origin, Bot relays, scheduled turns, Bots shared with friends, turns with no recorded origin, and sub-agents, which resolve to their parent Bot.$DSH_SECRET_GITHUB_TOKENwithgh/gitonly as the fallback (for examplegit push).Implemented in #656, on the same branch as the secrets change.
Declare DSH_SECRETS_WITHHELD so a withheld turn keeps its shell (Thin…
Give the owner's keys to every turn of the owner's own Bots (ThinkFlo…
Give connector tools to every turn of the owner's own Bots (ThinkFlow…
fix(turn-origin): fail closed on a source kind it does not recognise (T…
Give the owner's keys to every turn of the owner's own Bots (ThinkFlo…