Skip to content

fix(console): read the default slack account's token keys and show channel refusals on credentials - #3686

Merged
freeqaz merged 24 commits into
mainfrom
fix-postland-1025m
Oct 29, 2026
Merged

freeqaz merged 24 commits into
mainfrom
fix-postland-1025m

Conversation

@freeqaz

@freeqaz freeqaz commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Two gaps in the Agent page's Credentials tab, both next to the 10-25 Slack token rules (#3680, #3681):

  • Deploy blocked wrongly. With no named Slack accounts, missingChannelCredentialGroups always asked for SLACK_APP_TOKEN and SLACK_BOT_TOKEN. It ignored the keys the default account's tokens actually name and ignored HTTP mode, which reads no app token. A Configuration with appToken: "${ACME_SLACK_APP}" and botToken: "${ACME_SLACK_BOT}", both bound, is accepted by save and by the Slack Channel Driver, but the tab reported "Slack Secret bindings" missing and disabled Deploy new version. HTTP mode with a bound bot token and no app token had the same result.
  • Grant and binding gaps (review follow-up). Deploy checks that the Agent may operate every bound Secret, but the tab checked grants only for the keys the Slack tokens read. In HTTP mode with SLACK_APP_TOKEN still bound from Socket Mode and no Agent grant on it (or the standard pair left bound beside custom token keys), the tab said "Ready to deploy." with no Retry, and the deploy failed AUTHORIZATION_DENIED. The tab also listed only the tokens, so Deploy was available while save and deployment refuse an unbound signingSecret, userToken or relay.authToken reference.
  • Deploy ran ahead of the grant read (second review). With the Namespace IAM reads held, Deploy new version was enabled, and a click whose Configuration reread outlasted them sent the deploy request after the panel had shown the missing grant.
  • A save during the grant read dropped its answer (third review). A Save channel Secrets or Retry Secret access that started while the grant read was in flight made the panel discard that read, and nothing read the grants again. With SLACK_APP_TOKEN bound and ungranted, repointing only the bot token during the read granted only the new bot Secret: both rows showed Bound, the tab said "Ready to deploy.", and the deploy failed AUTHORIZATION_DENIED. The "Checking this Agent's credential access" status also flashed on every administrator page open.
  • Refusal hidden. Save channel Secrets resends the stored values. A stored Configuration that holds a Slack token form save now refuses (breaking change 2026-10-25) gets a 400 that names the setting. The tab replaced that message with "Check the selected Secrets and reload this Agent.". No Secret choice or reload fixes that.

Change

  • credentials.mts: with no named accounts, the tab reads each default-account token's key as the Slack Channel Driver does: a whole ${NAME} or an env SecretRef id. A token that names no key keeps the standard name. Deploy waits until each key is bound. The tab never lists fewer token bindings than save requires.
  • Pickers: when every key is SLACK_APP_TOKEN or SLACK_BOT_TOKEN, the form shows a picker for each of those keys only. In HTTP mode with botToken: "${SLACK_BOT_TOKEN}" and no app-token reference, the tab keeps the Slack bot token picker alone, which is the only console path that rebinds it. When any key is another name, the tab lists the keys as Bound or Missing without pickers and points to the Configuration API, as it already did for named accounts. The hint no longer says the listed keys are not the standard pair.
  • HTTP-mode app token: HTTP mode reads no app token, but save still requires a binding for each name a string app token references, $NAME, ${NAME:-text} and xapp-${NAME} included. The tab lists those names (environmentReferenceNames and secretInputShorthandName, now served to the console with unsubstitutedReference).
  • Pickers only for their own key: a picker appears only when its token reads its own standard key. Swapped standard keys (appToken: "${SLACK_BOT_TOKEN}") get the list, so no picker binds a Secret under the wrong token's label.
  • Every reference save requires: the tab lists every name the Slack settings reference that save requires a binding for, as requireChannelSecretBindings reads them: env SecretRef ids anywhere, ${NAME} without a fallback, and in secret-input fields (appToken, botToken, signingSecret, userToken, relay.authToken, on the channel and on each named account) also ${NAME:-text} and $NAME. Keys without a picker, such as SLACK_SIGNING_SECRET, are listed below the pickers and Deploy waits for them.
  • Grants on every bound Secret: grant checks, outstanding grants and Retry Secret access cover every Secret the Configuration binds, as the worker's deploy check does. A bound key that no Slack setting reads gets its own row, Bound · not read by the Slack settings, so its warning and Retry have a place. Retry grants only the Secrets that lack a grant and writes no Configuration.
  • Deploy waits for grant reads: while an Installation administrator's console reads the Agent's grants on the bound Secrets, or on the harness credential, Deploy is disabled and says "Checking this Agent's credential access before deploying.". The click handler also asks the Credentials panel again immediately before the POST in detail.mts, so a grant read that lands during its rereads stops the request.
  • Saves during a grant read: the panel numbers its grant reads, and only the latest one clears the Deploy hold or changes the rows. A save or Retry that overtook a read starts a new read when it ends, so Deploy stays held until the grants on the saved bindings are known. The checking text in the deploy status and in the checklist uses the console's 250 ms delayed-status span, so a read that settles sooner shows nothing. Deploy stays disabled during the read.
  • Refusals: a 400 that channelBindingRefusal matches shows the API's message plus where to fix it. A refusal that asks only for a binding ("… without a Configuration Secret binding") gets where the console binds one, the same text the Configuration JSON editor uses (CHANNEL_BINDING_PLACES in list.mts, now shared). Other refusals get "Fix that setting in the Configuration JSON on the Configuration tab: this tab saves only the SLACK_APP_TOKEN and SLACK_BOT_TOKEN bindings." The message names the setting and never contains token text. Other 400s keep the old text.
  • No rule is loosened. No Driver contract or API message changes.
  • Storybook (Components/Credentials): Slack tokens on other keys (a bot token on ${ACME_SLACK_BOT}), Slack HTTP mode, bot token only, Slack key left bound without an Agent grant, Slack signing secret without a binding, Slack grant read in progress, Channel Secret save refused for a channel setting and Channel Secret save refused for a missing binding. The fixture gained slackTokens and a list form of slackBindings.
  • CSS: the Channel Secrets label column grows with long environment keys instead of overlapping the badge.
  • Docs: Channel Secrets in channels-and-credentials.md, and the console editing flow.

Verification

  • tests/conformance/slack-configuration.test.mjs: custom ${NAME} keys, a mixed env SecretRef plus a missing token, HTTP mode with no app token, and HTTP-mode app tokens written as ${NAME}, ${NAME:-x}, xapp-${NAME}, $NAME, $SLACK_APP_TOKEN and two references.
  • tests/browser/console-runtime-credentials.test.mjs, four cases: (1) the default account's ${ACME_*} keys are listed Bound with the corrected hint, there are no pickers, and Deploy is enabled; (2) HTTP mode shows only the bot token picker, a rotation saves and Deploy stays available; (3) a stored file SecretRef bot token gets its refusal with the Configuration JSON pointer; (4) a stored unbound signingSecret reference gets its refusal with the binding remedy and no Configuration JSON pointer. (3) and (4) write the stored row through the storage Driver the way an earlier build stored it, under the bundled Kubernetes Compute rule, and get a real 400. Nothing is saved.
  • Second review: a browser case holds the IAM policy reads, checks that Deploy is disabled once the deploy status settles, releases them, sees the missing grant hold Deploy, and counts zero deploy requests; Retry Secret access then repairs the grant and Deploy becomes available. It fails at the Deploy-disabled assertion on de828d999c, the same branch without the timing fix.
  • Third review, two browser cases hold the panel's first grant read, rebind only the bot token and save: (a) the read is released after the save ends; (b) it is released while the save's Configuration write is held. In both, the app token row is marked, Deploy stays held with "Resolve the saved Secret access grant before deploying." and no deploy request is sent; Retry then grants the app Secret and Deploy becomes available. Both fail on 8a1a940e3f with "Ready to deploy.". The held-read case also checks that the checking text sits in the delayed-status span (fails on 8a1a940e3f). The three cases pass 20/20 at 2 CPUs on a remote test VM in a private network namespace. On ffddb8e28c (main merged incl. fix(console): show the API's typed refusal reasons through one shared describer #3690, fix(deploy): give every deployment failure code a message and a next step #3697), the credentials, channels, draft-guards, detail, detail-2 and overview browser files plus the Slack conformance file pass 315/315. The 51-shape probe output is unchanged.
  • Review follow-up, tests/browser/console-runtime-credentials.test.mjs: (5) a listed key without the Agent's grant, (6) HTTP mode with SLACK_APP_TOKEN left bound and ungranted, and (7) custom token keys with the standard pair left bound and SLACK_APP_TOKEN ungranted: each marks the row, holds Deploy, and Retry grants exactly that Secret with no Configuration write; (8) a signingSecret reference without a binding is listed Missing and holds Deploy; (9) swapped standard keys get the list and no pickers, checked before any copy. The HTTP-mode case now also expects the SLACK_SIGNING_SECRET row.
  • tests/conformance/slack-configuration.test.mjs: for seven shapes (signing secret, user token, relay token, fallbacks inside and outside secret-input fields, an env SecretRef in another field, named accounts), the test collects every name the real save rule requires, leaves each one unbound in turn, and expects the gate to hold Deploy.
  • Every new and changed case fails on the previous head d267ee8de6 (5 browser cases, the conformance case), and the swapped-keys case fails on 115df49efc at the picker assertion. All pass on this head (8a1a940e3f, main merged incl. fix(console): keep deploy open after a member's credential save and name bound secrets a deployer cannot operate #3692): the credentials, draft-guards, channels, detail, saves, drafts-kept and deploy-again browser files plus the conformance file 267/267; a wider run of 16 console browser files passed 461/462, the one failure a 250 ms layout-timing case under 8-way load that passes 6/6 on its own.
  • Pins at 2 CPUs on a remote test VM: the eight new and changed browser cases passed 20/20 (in a private network namespace), and the conformance file passed 20/20.
  • A probe of 51 Configuration shapes (the earlier 37, the review's two scenarios, and non-token Secrets) compares the tab's gate with the save rule and the Slack Channel Driver. Every shape save refuses for a missing binding is now blocked until that name is bound (0 of 23 left enabled; 12 of 23 on d267ee8de6). A browser probe of 14 grant shapes (standard, swapped, custom, HTTP, named accounts, leftover keys, signing secret, user token, relay token) holds Deploy whenever a bound Secret lacks the Agent's grant, and Retry grants exactly that Secret. Two shapes with every grant in place stay deployable with no grant writes.
  • tsc --build (console and tests), eslint, and prettier pass on the touched files.

Media at f0288e4a09 (this head). Console Storybook, Components/Credentials, rendered from prepare-assets.mjs output in headless Chromium: simulated UI proof (simulated API; no backend persistence, credential propagation or deployment). Before = main at the merge base be63cc7e19 with this PR's stories and fixtures copied in, so the same scenario renders on main's console code. After = this head. 1440 px full page, 390 px the Channel Secrets section (and the Prepare card where Deploy matters). These replace the earlier captures, which predate the text-only save message (132248437f) and main's Agent detail layout change. The browser tests above cover the real console and API.

A save during the grant read (story Slack grant read in progress: the policy read is held 4 s; the bot token is switched to the backup Secret and saved while the read runs; the app token Secret stays bound without the Agent grant).
Before (main): both rows Bound, "Ready to deploy." and Deploy enabled while the app grant is missing.
before: save during the grant read, 1440
before: save during the grant read, 390

After: the app row reads Bound · Agent access not confirmed, Retry Secret access is offered, the save says only "Channel Secret bindings saved." and Deploy waits with "Resolve the saved Secret access grant before deploying."
after: save during the grant read, 1440
after: Deploy held, 390
after: Channel Secrets, 390

After Retry Secret access: "This Agent can use its saved Slack Secrets. Deploy the new version to deliver them." and Deploy is available. No deploy request was sent during the capture.
after Retry, 1440

Slack key left bound without an Agent grant (HTTP mode, SLACK_APP_TOKEN still bound and ungranted). Before (main): both standard pickers, with the app token treated as read.
before: leftover key, 1440
before: leftover key, 390

After: the bot token picker alone and a SLACK_APP_TOKEN row, Bound · not read by the Slack settings · Agent access not confirmed; Deploy is held and Retry is offered.
after: leftover key, 1440
after: leftover key, 390

After Retry Secret access: the row reads Bound · not read by the Slack settings and Deploy is available.
after: leftover key after Retry, 1440

Slack signing secret without a binding. Before (main): "Ready to deploy." although save and deployment refuse it.
before: signing secret, 1440
before: signing secret, 390

After: SLACK_SIGNING_SECRET is listed Missing below the pickers and Deploy waits for it.
after: signing secret, 1440
after: signing secret, 390

Slack grant read in progress (no interaction, captured while the read is held). Before (main): "Ready to deploy." and Deploy enabled.
before: grant read in progress, 1440
before: grant read in progress, 390

After: "Checking this Agent's credential access before deploying." and Deploy disabled until the read lands.
after: grant read in progress, 1440
after: grant read in progress, 390

Slack HTTP mode, bot token only. Before (main): the app token is listed Missing, Deploy is disabled and Save stays disabled.
before: HTTP mode, 1440
before: HTTP mode, 390

After: the bot token picker alone and "Ready to deploy."; rotating it to another Secret saves.
after: HTTP mode, 1440
after: HTTP mode rotation saved, 1440
after: HTTP mode rotation saved, 390

Slack tokens on other keys (bot token ${ACME_SLACK_BOT}, unbound). Before (main): the standard pair with pickers, and Deploy names no key.
before: other keys, 1440
before: other keys, 390

After: the keys the tokens read, ACME_SLACK_BOT Missing, no pickers, the Configuration API hint, and Deploy waits for ACME_SLACK_BOT.
after: other keys, 1440
after: other keys, 390

Channel Secret save refused for a missing binding. Before (main): "Check the selected Secrets and reload this Agent."
before: binding refusal, 1440
before: binding refusal, 390

After: the API message plus where the console binds a Secret, with no Configuration JSON pointer.
after: binding refusal, 1440
after: binding refusal, 390

Channel Secret save refused for a channel setting. Before (main): the same generic text.
before: setting refusal, 1440
before: setting refusal, 390

After: the API message plus the Configuration JSON pointer.
after: setting refusal, 1440
after: setting refusal, 390

Videos at f0288e4a09, 1440 px: save during the grant read, then Retry, leftover key, Retry Secret access, HTTP-mode bot token rotation, refused save for a missing binding, refused save for a channel setting.

Known at 390 px: the status badges do not wrap, so a long one runs past the card (on main too, for "Bound · Agent access not confirmed"); the new "Bound · not read by the Slack settings · Agent access not confirmed" badge widens the page. Tracked separately.

Risks

  • A default account whose tokens name another key, for example ${ACME_SLACK_BOT}, loses the pickers on this tab and gets the named-account treatment: bind through the Configuration API. Before this change those pickers bound keys the tokens did not read.
  • An administrator's Deploy is disabled for the length of one IAM policy read when the page opens (and when the Credentials tab reads the harness credential's grant). A member's session reads no policy and is not delayed.
  • A save or Retry that overtakes a grant read adds one more IAM policy read when it ends. Saves that do not overlap a read are unchanged.
  • The pre-POST recheck covers what the panel knows; the API's deploy check stays the authority.
  • With Slack enabled, the Channel Secrets list now also shows bound keys that no Slack setting reads (for example a leftover SLACK_APP_TOKEN, or a binding another setting uses), because deploy checks their grants too.
  • SLACK_SECRET_INPUTS in the console mirrors the OCC list in channel-secret-bindings.ts; the conformance test runs the real save rule, so a drift that lists fewer names fails it.
  • Still open, as on main: a stored token form that save refuses (file SecretRef, $NAME, pasted value) with its names bound leaves Deploy enabled; the tab's save and deploy both refuse those and name the setting. Grant checks still run only while Slack is enabled.
  • The Slack editor on the Channels tab is unchanged.

…annel refusals on credentials

The Credentials tab asked for SLACK_APP_TOKEN and SLACK_BOT_TOKEN whenever
there were no named accounts, so a default account whose tokens name other
keys, or HTTP mode with no app token, held Deploy for a Configuration that
save and the Slack Channel Driver accept (finding 2526). It now reads each
token's key as the driver does and lists those keys without pickers when
they are not the standard pair.

A channel Secret save resends the stored values, so a stored token form
that save now refuses got a 400 naming the setting, which the tab replaced
with "Check the selected Secrets and reload this Agent." (finding 2529). It
now shows the API's message and points to the Configuration JSON.
@freeqaz
freeqaz requested a review from a team as a code owner October 25, 2026 23:12
@clawsweeper

clawsweeper Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. labels Oct 25, 2026
@clawsweeper

clawsweeper Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

What this changes

The Console reads Slack’s configured credential keys, preserves grant checks across saves, and shows actionable channel-save refusals.

Example: Slack uses ${ACME_SLACK_APP} and ${ACME_SLACK_BOT}, with both Secrets bound and granted.

  • Before: Credentials asks for the standard Slack keys and disables Deploy.
  • After: Credentials lists both ACME keys as Bound and allows Deploy.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The patch has meaningful regression coverage and passing required CI; the final visible recovery message still needs current visual proof.
Proof confidence 🦐 gold shrimp (3/6) Real behavior proof is not required for maintainer- or bot-authored pull requests.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Valid Slack configurations cannot deploy, missing Secret grants can appear ready, and refused saves can give an ineffective recovery instruction.
Reason: The maintainer-authored direction aligns Console readiness with existing Configuration, IAM, and deployment contracts without adding an option or changing a Driver contract.

Merge readiness

✅ Ready for maintainer review

Keep this PR open: it fixes credential-readiness bugs still present on main, and the prior findings are addressed; no remaining code defect was found.

Priority: P2
Reviewed head: f0288e4a09b6527bb7ffda746fc29218ffd43dd0

Before merge

None.

Findings

None.

Tests

  • Missing end-to-end proof: Refresh the visual walkthrough of the overlapping save on the final UI, showing the short confirmation while the app grant is missing and deployment guidance only after access is confirmed.
Agent review details

How this fits together

The Agent Credentials panel reads saved Configuration bindings and Namespace IAM policy, displays missing bindings or grants, and gates deployment requests; OCC and the worker retain final authorization.

flowchart TB
  A[Saved Configuration] --> B[Credentials panel]
  C[Namespace IAM policy] --> B
  B --> D[Binding rows and Deploy gate]
  B --> E[Exact Secret grant API]
  D --> F[Deployment API]
  F --> G[Worker authorization]
  E --> C
Loading

Technical review

Best possible solution:

Land the existing credential-panel repair after its corrected final UI has reviewable visual evidence.

Do we have a high-confidence way to reproduce the issue?

Current main directly shows the fixed-key readiness check; supplied browser regressions and recorded reproductions establish the grant-read races, but this review executed no target code.

Is this the best way to solve the issue?

The patch repairs the existing Credentials owner, preserves authoritative server authorization, and adds meaningful coverage for missing bindings, grant recovery, refusal messages, and both save/read orderings.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against cadab7afee96.

Provenance checked

Testing

Proof path: in-process harness.

Security

None.

Evidence

What I checked:

  • Current main still has the reported default-key bug: At fetched main, default-account readiness still requires SLACK_APP_TOKEN and SLACK_BOT_TOKEN regardless of the configured token references or HTTP mode. (apps/controller/src/console/agents/credentials.mts:92, cadab7afee96)
  • Introduced changes and race handling: The pinned diff derives required keys from saved Slack settings, checks every bound Secret, numbers grant reads, rereads after overlapping saves, and rechecks readiness immediately before deployment. (apps/controller/src/console/agents/credentials.mts:1299, f0288e4a09b6)
  • Prior findings addressed: The HTTP picker description now distinguishes configurations retaining an app-token reference; saveOutcomeText omits the Deploy instruction while checks or grants remain outstanding, with negative and positive browser controls for both response orderings. (docs/guides/console/channels-and-credentials.md:129, f0288e4a09b6)
  • Visual evidence remains historical: The inspected partial-save screenshot shows the old contradictory Deploy instruction above Retry; the author explicitly confirms in the latest update that media predates the correction. (apps/controller/src/console/agents/credentials.mts:884, f0288e4a09b6)
  • Authority remains with existing IAM and worker owners: Retry uses the existing exact-Namespace grant API, which reauthorizes inside the mutation transaction; the worker reauthorizes both actor and Agent before Secret delivery. The related grant work supplies an inspected authority trace showing denied-reader writes persist no grant and queued revocation prevents workload and Secret delivery; this is supporting proof of the shared backend, not execution of this PR’s UI. (packages/occ/src/index.ts:2466, f0288e4a09b6)
  • Exact-head CI: CI Required, both Console Browser lanes, and both Checks and Conformance lanes pass on the reviewed head; First Agent Smoke and JavaScript/TypeScript CodeQL remain in progress. (f0288e4a09b6)

Likely related people:

  • Free Wortley: Raw commit 01e1a7f adds apps/controller/src/console/agents/credentials.mts:58 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 01e1a7fab2b0; files: apps/controller/src/console/agents/credentials.mts)

Labels

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.
  • remove proof: 📸 screenshot: Current real behavior proof evidence kind is not_applicable.

Label justifications:

  • P2: The repair addresses incorrect deployment readiness and recovery for specific Slack configurations and overlapping credential saves.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

History

Review history (2 earlier review cycles)
  • reviewed 2026-10-25T23:25:39.006Z sha 1bad04b :: needs maintainer review before merge. :: none
  • reviewed 2026-10-26T01:44:05.235Z sha ffddb8e :: needs changes before merge. :: [P3] Qualify the bot-only HTTP picker description

Reviewed October 29, 2026, 7:25 AM ET / 11:25 UTC (Revision 3).

…token reference

Review findings on this PR:
- 2540: a default account whose tokens read only standard keys (HTTP mode with
  botToken ${SLACK_BOT_TOKEN}) keeps pickers for just those keys instead of the
  custom-keys list, so the bot token can still be rebound in the console. The
  other-keys hint no longer says the listed keys are not the standard pair.
- 2541: in HTTP mode the tab also lists each name a string app token references
  ($NAME, ${NAME:-x}, xapp-${NAME}), as save requires a binding for each.
- 2542: a save refused for a reference without a binding gets where the console
  binds one (shared with the Agent page editor's remedy), not the Configuration
  JSON pointer.
# Conflicts:
#	apps/controller/src/console/environment-references.d.mts
#	apps/controller/src/console/environment-references.mjs

@freeqaz-openai freeqaz-openai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I reproduced two cases on 115df49e using the CI-built Storybook and synthetic Secret bindings.

  1. Deploy becomes available when the Agent still lacks access to a bound Secret. In the “Slack Secret bound without an Agent grant” story, leave the Agent without its grant for SLACK_APP_TOKEN. Keep that app binding and change the bot reference to ${ACME_SLACK_BOT} with its binding present. Both rows now say Bound, Retry Secret access disappears, and Deploy new version becomes enabled. With the standard bot key, the app row shows Agent access not confirmed and Deploy stays disabled. In checkBoundSecretGrants, deriving the read from slackFormBindings(...) ?? [] skips the entire check when any key requires the read-only list. Please check the Secrets the default account actually references, independently of which pickers it can show, and keep the missing-grant warning and recovery available.
  2. Swapping the two standard references reverses what the picker labels mean. Set appToken to ${SLACK_BOT_TOKEN} and botToken to ${SLACK_APP_TOKEN}. The form still shows both role-labelled pickers, but the Slack app token picker writes the key the bot reads. Please compare each token role with its effective binding before offering that picker. The existing read-only list is a suitable fallback. The ordinary HTTP configuration with only a standard bot token should keep its bot picker.

I also opened the custom-key and HTTP previews and pressed Save channel Secrets in the refusal preview. The alert received focus and named the setting and the Configuration JSON repair location.

…heck grants for listed keys

Review on 115df49:
- A default account whose tokens read each other's standard key (appToken
  ${SLACK_BOT_TOKEN}, botToken ${SLACK_APP_TOKEN}) got role-labelled pickers that
  bound the other token's key. The form now requires each token to read its own
  standard key; other shapes get the list. HTTP mode with only a standard bot
  token keeps its bot picker.
- In list mode the administrator's grant check was skipped, so a listed key whose
  Secret the Agent may not use showed Bound and Deploy was enabled. Grant checks
  and pending grants now cover every listed key, and list mode keeps a Retry
  Secret access button for them.
- The other-keys hint now states what the pickers bind and that the tokens read
  the listed keys, which holds for every list shape.
# Conflicts:
#	apps/controller/src/console/agents/credentials.mts
#	tests/browser/console-runtime-credentials.test.mjs
@freeqaz

freeqaz commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, both reproduced and fixed in 7aa5946 (head d267ee8, which also merges main).

  1. Grant check for listed keys. checkBoundSecretGrants and the outstanding-grant tracking (referencedSecretIds) now read every key the tab lists (slackBindingKeys), independent of the pickers. In list mode the form keeps only Retry Secret access, shown while a retryable grant is outstanding. Your case (standard app key without its grant, bot on ${ACME_SLACK_BOT}) now shows Bound · Agent access not confirmed, holds Deploy with "Resolve the saved Secret access grant before deploying.", and Retry grants it without writing the Configuration. Named accounts get the same check and Retry.
  2. Swapped references. The form now requires each token to read its own standard key (app token SLACK_APP_TOKEN, bot token SLACK_BOT_TOKEN) before it offers role-labelled pickers. appToken: ${SLACK_BOT_TOKEN} with botToken: ${SLACK_APP_TOKEN} gets the read-only list. HTTP mode with only a standard bot token keeps its bot picker. The list hint now says what the pickers bind and that the tokens read the listed keys, so it holds for this shape too.

New browser cases in console-runtime-credentials.test.mjs cover both (they fail on 115df49). Pinned 20/20 at 2 CPUs with the other changed cases, and the credentials, channels and saves browser files plus the Slack conformance file pass in full. Screenshots and a video of the Retry flow are in the PR body.

…ference save requires

Deploy checks that the Agent may operate every bound Secret, but the
Credentials tab checked grants only for the keys the Slack tokens read, so
a leftover SLACK_APP_TOKEN in HTTP mode (or the standard pair beside custom
token keys) showed Ready to deploy with no Retry and the deploy failed
AUTHORIZATION_DENIED (finding 2558). Grant checks, outstanding grants and
Retry now cover every bound Secret, and each bound key no Slack setting reads
gets its own row.

The tab also listed only the tokens, while save refuses an unbound
signingSecret, userToken or relay.authToken reference (finding 2554). It now
lists every name save requires a binding for.

@freeqaz-openai freeqaz-openai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I verified the two reported corrections on d267ee8. Crossed token references now show a read-only list. With a custom bot key and a missing app-Secret grant, the list shows the warning, offers Retry, and disables Deploy. Retry sent only the expected grant request and then enabled Deploy.

One timing case still sends a deployment request after the Console knows the grant is missing. In the CI-built Storybook I held the grant reads, clicked Deploy new version, and paused the click handler's Configuration reread. I then released the grant reads and observed Bound · Agent access not confirmed on the app row. Releasing the Configuration reread sent a POST to the simulated Agent deployment endpoint.

Please keep Deploy waiting while its relevant grant read is in flight and check the credential panel's current readiness again immediately before the POST in apps/controller/src/console/agents/detail.mts. A regression test can control the response order above and assert zero Deploy requests. Keep the positive control where Retry repairs the grant and Deploy becomes available. This is a Console request-ordering finding; the reproduction used the fixture backend.

…a leftover key and a signing secret

Storybook gains Slack key left bound without an Agent grant (HTTP mode with
SLACK_APP_TOKEN still bound) and Slack signing secret without a binding.
Channel Secrets rows can now hold long environment keys, so the label
column grows with them instead of overlapping the badge.
…edentials before the deploy request

Review on d267ee8: with the grant reads held, Deploy new version was
enabled, and a click whose Configuration reread outlasted them sent the
deploy request after the panel showed the missing grant. Deploy now waits
for the Credentials panel's grant read (and the harness credential's),
and the click asks the panel again just before the POST.
One message for both grant reads: Checking this Agent's credential access
before deploying. Storybook gains Slack grant read in progress.
# Conflicts:
#	apps/controller/src/console/agents/credentials.mts
@freeqaz

freeqaz commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, reproduced and fixed in this push (head 8a1a940, which also merges main through #3693).

Your timing case. Deploy now waits for the grant read:

  • While an Installation administrator's console reads the Namespace IAM policy for the Agent's grants (the Credentials panel's read of the bound Secrets, and the harness credential's read on the Credentials tab), Deploy new version is disabled and says "Checking this Agent's credential access before deploying.".
  • The click handler asks the Credentials panel again immediately before the POST in detail.mts, after its Agent and Configuration rereads. A grant read that lands during them stops the request with the panel's own message.
  • Regression test: Deploy waits for the Agent's Secret grant read and sends no deploy it would refuse holds the policy reads, checks that Deploy is disabled once the deploy status settles, releases them, sees the missing grant hold Deploy, and counts zero deploy requests. The positive control follows: Retry Secret access grants the Secret and Deploy becomes available. It fails at the Deploy-disabled assertion without the fix. Storybook has Slack grant read in progress for the held read.

Also in this push (internal re-review of d267ee8 found these):

  • Grants on every bound Secret. The worker checks that the Agent may operate every bound Secret, but the tab checked only the keys the Slack tokens read. HTTP mode with SLACK_APP_TOKEN still bound and ungranted, or the standard pair left bound beside custom token keys, showed "Ready to deploy." with no Retry. Grant checks, outstanding grants and Retry now cover every bound Secret. A bound key that no Slack setting reads gets its own row, Bound · not read by the Slack settings.
  • Every reference save requires. The tab now lists every name the Slack settings reference that save requires a binding for, including signingSecret, userToken and relay.authToken on the channel and on named accounts. A conformance test runs the real save rule and checks that the gate never asks for fewer bindings.
  • The swapped-references test now fails on 115df49 at the picker assertion, not at the hint text.

All new and changed cases pass 20/20 at 2 CPUs. Before/after screenshots and videos are in the PR body.

@clawsweeper

clawsweeper Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Oct 26, 2026

@freeqaz-openai freeqaz-openai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

On ffddb8e28c, the Deploy hold now works. In the CI-built Storybook, changing only the bot Secret while the first reads were pending sent zero deployment requests with the app grant missing and one with it present.

I also exercised the initially granted case against the real Controller in the local browser fixture on the CI-tested tree. Releasing the original read before or after the save gave the same result: one deployment POST, HTTP 202, and one stored revision. Please keep this positive assertion alongside the missing-grant case in the owning test.

Please fix the success message immediately above Retry Secret access. After all reads settle, the missing-grant case says both “deploying it would be refused” and “Channel Secret bindings saved. Deploy the new version to deliver them.” The latter comes from credentials.mts:1126, and the replacement read updates the warning and rows while leaving that success message visible. “Channel Secret bindings saved.” is enough when a grant recheck is pending. Please assert that the settled missing-grant state offers Retry without directing the user to Deploy. The existing Claw review also covers the HTTP picker guide and the remaining deployment-test coverage.

# Conflicts:
#	apps/controller/src/console/agents/detail.mts
#	docs/flows/platform-console/agent-editing.md
#	tests/browser/console-runtime-credentials.test.mjs
#	tests/helpers/refusal-text.mjs
…ss is confirmed

A save that ended with a grant read still to come, or with a bound Secret
still missing its Agent grant, said "Deploy the new version to deliver them"
above Retry Secret access while the deploy gate held Deploy. The save
message now says only "Channel Secret bindings saved." (or the Retry
equivalent) until the read settles with nothing missing.

The owning browser test asserts the settled missing-grant state offers Retry
without pointing to Deploy, and adds the granted positive control: one deploy
POST, HTTP 202, one stored version. The guide qualifies the HTTP bot-only
picker sentence: HTTP mode with appToken "${SLACK_APP_TOKEN}" shows both.
… grants with a channel runtime

The positive control deploys, so its fixture evaluates the grants the
console commits through the IAM API and admits Slack with an Installation
channel runtime. The deploy status assertion carries the API's answer on
failure. The short save message is asserted at save end only for the
missing-grant cases: with the grant present, the save's unheld recheck may
already have settled and named Deploy.
… the main merge

Main brought the page to 2498 words and this PR's Credentials trace pushed the
merge to 2690. Condense the channel Secret grant paragraph and the step 5 grant
note, fold this PR's changelog lines into one, and move the older dated entries
to the history page. The docs word counter reports 2495.
@freeqaz

freeqaz commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Free's clanker here (an AI agent working on Free's behalf). This push addresses the review on ffddb8e28c and the open Claw review item. Head: e2083d3d66.

Merge with main. The branch conflicted with main; it now includes main through 62a00c3c51. Conflicts were in agents/detail.mts (this PR's 250 ms delayed checking text now sits beside main's credentialRejected() guard on the read-only suffix), the flow changelog, the test imports and tests/helpers/refusal-text.mjs (both sides' exports kept).

Success message above Retry Secret access (credentials.mts, saveOutcomeText). A save or Retry names Deploy only when no grant read is still to come and no bound Secret is missing its Agent grant. Otherwise it says only "Channel Secret bindings saved." (or "This Agent can use its saved Slack Secrets."). When the replacement read settles, the message is restated. With a missing grant it stays at the short form beside the warning and Retry. With every grant present it gains "Deploy the new version to deliver them." again.

Owning test (tests/browser/console-runtime-credentials.test.mjs, saveOvertakesGrantRead), both response orderings:

  • Missing app grant: at save end the status reads exactly "Channel Secret bindings saved.". In the settled state the app row warns, Retry Secret access is enabled, the Channel Secrets section has no "Deploy the new version" text, and there are zero deploy POSTs. Retry then repairs the grant and Deploy becomes available.
  • App grant present (new positive control, next to the missing-grant cases): Deploy reads "Ready to deploy.", the save message names Deploy, one deploy POST returns HTTP 202, and exactly one new stored version has the deployed ID. The fixture evaluates the grants the console commits through the IAM API (provisionedPeople: []) and enables the Installation channel runtime, so the API admits the Slack deploy as it would in production.

Guide (channels-and-credentials.md). The HTTP sentence now covers only HTTP mode without an appToken reference. HTTP mode that keeps appToken: "${SLACK_APP_TOKEN}" shows both pickers. The flow page (agent-editing.md) went over the 2500-word limit after the main merge (2690). The grant paragraph is now shorter, and older changelog lines moved to its history page. The docs word counter now reports 2495.

Verification (a remote test VM, browser tests in a private network namespace):

  • The two missing-grant cases fail on ffddb8e28c with only the new test applied. The short-message assertion gets "Channel Secret bindings saved. Deploy the new version to deliver them.". Both pass on this head.
  • The four save-overlap cases passed 20/20 at 2 CPUs.
  • console-runtime-credentials, console-agent-channels and console-agent-draft-guards: 101/101.
  • tsc --build is clean, and docs/build.mjs --check validated 305 pages.
  • Not refreshed: the PR's before/after media. This change is a status-message text change and no layout changes.

@freeqaz-openai
freeqaz-openai dismissed their stale review October 28, 2026 08:21

Verified on e2083d3: the missing-grant cases keep Retry available without directing the operator to Deploy. In both response orders, the granted cases assert one deployment POST, HTTP 202, and one matching new revision. All four passed in the hosted credentials suite. The guide now distinguishes the two HTTP picker configurations. This resolves the findings in this review.

…t of the agent editing flow

The main merge brought back three changelog entries that this branch had
already moved to agent-editing/history.md, putting the page at 2543 words.
Without the duplicates it is 2496.
@freeqaz

freeqaz commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Free's clanker here (an AI agent working on Free's behalf). I synced this branch with main; head is now f0288e4a09. No product behaviour changed in this push.

Merge with main (ad11853f15). Two conflicts:

  • apps/controller/src/console/agents/detail.mts: main replaced the readOnly constant with draftReadOnly(). This branch's 250 ms delayed "Checking this Agent's credential access before deploying." text (finding 2576) now uses draftReadOnly() in both places; the rest is main's code unchanged.
  • docs/flows/platform-console/agent-editing.md changelog: main's new 10-28 and 10-29 entries are kept above this branch's 10-27 entry.

Docs follow-up (f0288e4a09). The merge brought back three changelog entries that this branch had already moved to agent-editing/history.md, which put the page at 2,543 words. I removed the duplicates, and the page is now 2,496 words (real counter).

Verification on the merged tree (remote test VM): checks-browser, checks-browser-2 and checks-baseline-1 passed on ad11853f15. Static Checks and checks-baseline-2 (typecheck) passed on f0288e4a09. For a mutant check, I made saveOutcomeText always append the Deploy sentence. console-runtime-credentials.test.mjs then failed 2 of 28 cases (for example, "a grant read that lands during a channel Secrets save is read again after it"); the unmutated file passes.

Media: the screenshots and video in the body predate the 10-27 text-only change to the success message above Retry Secret access.

@freeqaz

freeqaz commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. labels Oct 29, 2026
@freeqaz
freeqaz merged commit 8c8397d into main Oct 29, 2026
56 checks passed
@freeqaz
freeqaz deleted the fix-postland-1025m branch October 29, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants