Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(app-shell): useAutoRunCreate strips ?runAction=create_environment even when the toolbar has no create action, destroying the deep link unrecoverably #4123

Closed

Description

@yinlianghui

Observation recorded while investigating #3803 (whose own premise did not survive measurement — see that card). Filed separately because it is a different defect from #3803's hypothesis and is out of that card's scope.

Fact

packages/app-shell/src/environment/EnvironmentListToolbar.tsx arms useAutoRunCreate from the presence of any toolbar action, not from the presence of the create action:

const autoRunCreate = useAutoRunCreate(toolbarActions.length > 0 ? ctaKind : null);

toolbarActions is filtered only by locations.includes('list_toolbar'). So when the toolbar renders actions but none of them is create_environment, shouldRun still becomes true, and the effect strips runAction from the URL — while renderedActions attaches autoTrigger to nothing, because that only happens for a.name === CREATE_ACTION.

Measured on origin/main (sha bfdf3d419) with the real action:bar + action:button runner, a toolbar carrying a single non-create action and ?runAction=create_environment in the URL:

no-create-action: urlParam=null execute=0

The param is gone and nothing ran. Because consumption is modelled as "strip the URL", the signal is unrecoverable — a reload cannot retry the deep link, which is exactly the "one-shot, so refresh does not re-open" property working against the user here.

Why it may matter

This is the #844 welcome-page flow ("Create your environment" CTA deep-linking into the list). The user lands on the list, no dialog opens, and the URL no longer carries the intent.

Reachability in a real deployment is not demonstrated and is the open question for triage: it needs a toolbar that has at least one list_toolbar action but no create_environment. Two candidate paths, neither confirmed against real cloud metadata:

  • the cloud does not serve create_environment for this org/plan while still serving other toolbar actions;
  • create_environment declares requiredPermissions the caller lacks, so action:bar's capability gate (ADR-0066 D4) filters it out of the rendered bar — note the toolbar's own toolbarActions is computed before that gate, so the two disagree by construction.

The second path is the interesting one: the arming condition and the rendering condition are evaluated by different code with different filters, so they can diverge whenever the bar drops an action the toolbar counted. Filed as observation-class (finding, no pm:queue) per objectstack#4949 — severity self-assessed at filing time is unreliable, so triage should grade it.

Sketch of a fix direction (not implemented)

Arm on the create action specifically rather than on toolbarActions.length — gate useAutoRunCreate on whether toolbarActions actually contains an action named create_environment, so the URL is only consumed when there is something to trigger. Whether the capability-gate divergence should also be closed (toolbar and bar applying the same filter) is a separate design question.

Related


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    Contributor

    Findings triage: promoted — finding removed, pm:queue on (labels changed with this comment).

    • Premise re-verified at origin/main @ 92c0b1f: EnvironmentListToolbar.tsx:146 still arms on toolbarActions.length > 0 ? ctaKind : null, and the count/render divergence the card describes (toolbar counts before the capability gate, action:bar filters after) is unchanged.
    • Why promoted rather than held for the reachability question: the defect mechanism is measured with the real runner (urlParam=null execute=0), the failure destroys a user-intent signal unrecoverably (the one-shot strip), and the fix direction is narrow and safe regardless of which reachability path is live — arm on the presence of the create action itself instead of any toolbar action. That fix is correct even if today's cloud metadata never hits the divergence; the arming condition is simply wrong as written. The larger design question (should toolbar and bar share one filter) can stay out of scope, as the body already suggests.
    • Dedup per the card's search: clean; feat: URL-driven debug/developer panel (à la amis devtools) #844 is the flow's origin, finding(app-shell): useAutoRunCreate 在 render 阶段读 ref,?runAction=create_environment 的 autoTrigger 可能被无关 re-render 抹掉 #3803 the investigation that spawned this.
    • Target grading is the objectui whole-repo seat's call.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 10, 2026
  3. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    ContributorAuthor

    Claim: PM loop round 6 (rolling slot refill)
    Session: session_017Qqyix2QcnpUC9XeYVDzx3 · Branch: claude/issue-4123-autorun-arm-predicate · Worktree: objectui-issue-4123
    File surface: packages/app-shell/src/environment/EnvironmentListToolbar.tsx (useAutoRunCreate arming predicate) + tests. Serial constraints: disjoint from #7100 (metadata-admin), #3782 (docs/scripts), #3811 (console routes + i18n packs), #4149 (root doc) in flight; no i18n keys involved.
    Container & model: S, mode:subagent, model: opus

    Direction (investigate-then-implement): FIRST measure the open reachability question the card poses — can ADR-0066 D4's capability gate filter the create action out AFTER the toolbar counted it (arming and rendering using different filters by construction)? Whatever the answer, the fix direction is already sound because the current failure mode is strictly wrong: arming on toolbarActions.length > 0 strips ?runAction=create_environment unrecoverably while triggering nothing. Re-key the arm on the CREATE ACTION'S presence in the SAME post-filter list the bar renders (one predicate, one list — arming and rendering cannot disagree by construction), pin the measured D4-filtered case (deep link + create action filtered out → param SURVIVES or degrades recoverably, never silently swallowed), and keep the #3803-verified consumption ordering untouched (runner consumes before the parent strips — don't disturb that machinery, it was measured correct).


    Generated by Claude Code

  4. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    ContributorAuthor

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4166.

    Phase 1 answered the card's open question by measurement with the real component stack: the ADR-0066 D4 capability gate is the divergence path (toolbar filtered placement only; the bar additionally applies mayInvoke), reproduced as the card's exact urlParam=null execute=0 signature with ordinary metadata. The fix is the structural one: the toolbar joins useCapabilityGate — the shared hook built precisely for surfaces that filter their own lists, which this was a fourth surface bypassing — so arming and rendering read ONE list and cannot disagree by construction. Three judgment calls, each defended correctly: the unsatisfiable-arm degradation LEAVES the URL (consumption is the strip; a preserved one-shot intent is honored by the next capable mount — pinned), the ?runAction=X generalization was DECLINED after measuring that no generic contract exists, and the two sibling affordances (skeleton, upgrade CTA) moved to the gated list because leaving them would re-create the two-lists bug in miniature. #3803's verified ordering untouched and re-pinned; the reverse verification's 3-red/7-green split is attribution, not a gap, and was predicted.

    #4162 (autoTrigger silently dropped by action:menu on overflow — measured one layer deeper, probe deleted before commit) is a genuine design fork and is correctly held observation-class. Flipping ready + auto-merge.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions