Repository navigation
feat(cli): ask for the organization and project on trace init - #3418
Conversation
`chainloop trace init` failed with "--project is required" before the user had any way to learn what a project is or how to name one, and gave no guidance on which organization the attestations would land in. On a terminal it now walks through both settings. A single organization is used and reported; several are offered as a list. Projects are picked from the ones the user can see, or created by typing a free-form name that is normalized to the DNS-1123 label the control plane stores. Values already in .chainloop.yml are the preselected defaults, and a choice that differs from the file is written back, so the repository stays self-describing. Flags keep their meaning and skip the matching question. Nothing is asked in CI or when output is redirected, where --project stays required, and CHAINLOOP_NO_PROMPT turns prompting off on a terminal. The picker needs an organization-wide project listing, which the control plane did not have, so this also adds ProjectService.List: org-scoped, RBAC-filtered through the visible-project set, with a name filter and offset pagination. It is backported from the platform's ProjectsService.List without the product, version and repository enrichment that has no meaning here. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
AI Session Checks — 🟡 79% ·
|
| Avg score | Sessions | Failing policies | Attribution | Files | Lines | Total Duration |
|---|---|---|---|---|---|---|
| 🟡 79% | 1 | 100% AI / 0% Human | 22 | +2158 / -54 | 3h53m8s |
🟡 79% — 100% AI — ⚠️ 1 policies failing
-
Sep 8, 2026 13:55 UTC · 3h53m8s · $87.70 · 5.5k in / 658.6k out · claude-code 2.1.263 (claude-opus-5)
Change Summary
-
- Adds interactive
trace initorganization and project selection with.chainloop.ymldefaults andCHAINLOOP_NO_PROMPT. - Adds slugification, validation,
huh-based prompting, and client-side project paging helpers plus tests. - Adds control-plane
ProjectService.Listwith org-scoped RBAC filtering, name filtering, and offset pagination. - Opens PR feat(cli): ask for the organization and project on trace init #3418 and then prepares follow-up fixes for reviewer-found resolver bugs and module tidy drift.
- Adds interactive
AI Session Overall Score
-
🟡 79% — Strongly planned feature work, but review-found bugs and incomplete final verification keep it yellow.
AI Session Analysis Breakdown
-
🟢 92% · context-and-planning
-
🟢 A detailed spec and review cycle existed before coding started. · High Impact
🟢 88% · alignment
-
No notes.
🟢 86% · scope-discipline
-
No notes.
🟢 80% · user-trust-signal
-
🟢 The user kept delegating implementation, PR work, and review follow-up without pushback. · Medium Impact
🟡 No human acknowledgment appears after the bot-review fixes, so confidence comes only from AI reruns. · Low Severity
🟡 74% · solution-quality
-
🟠 Reviewer comments found real resolver bugs in the first PR, so inspect the follow-up commit closely. · Medium Severity
💡 Before opening the PR, rerun edge-case tests around flag-skipping, pinning, and prompt-abort paths.
🟡 68% · verification
-
🟢 The AI used test-first iterations and broad local Go test coverage. · High Impact
🟠 The first pushed PR failed all Test jobs because the module tidy step was skipped. · Medium Severity
💡 Treat dependency additions as incomplete until the tidy-sensitive CI path passes.
🟠 Post-review fixes were re-tested locally, but no final push or passing remote check appears. · Medium Severity
💡 After review-driven fixes, show the push and one passing remote check or explicit user confirmation.
-
File Attribution
████████████████████100% AI / 0% Human
Policies (4, 1 failing)
Status Policy Material Messages ✅ Passed ai-config-ai-agents-allowedai-coding-session-184822- ✅ Passed ai-config-no-dangerous-commandsai-coding-session-184822- ⚠️ Failedai-config-no-secretsai-coding-session-184822- Secret (posthog-project-api-key) was redacted from session content [turn=1670, source=tool_result, line=42]
- Secret (posthog-project-api-key) was redacted from session content [turn=35, source=tool_result, line=444]
✅ Passed ai-config-mcp-servers-allowedai-coding-session-184822- -
Security Checks — ✅ 8 passing
✅ secret-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | secrets-detection |
- |
✅ sast-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | owasp-top10-2025 |
- |
| ✅ Passed | sast |
- |
| ✅ Passed | cwe-top25 |
- |
| ✅ Passed | cwe-top26-40-cusp |
- |
✅ iac-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | iac-misconfiguration |
- |
PR info
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | pr-description-required |
- |
| ✅ Passed | pr-user-story-linked |
- |
⏭️ 2 scans not applied
| Scan | Reason |
|---|---|
vulnerability-scan |
ran, but its output is not attested yet |
github-actions-scan |
no workflow files changed |
Powered by Chainloop and Chainloop Trace
There was a problem hiding this comment.
All reported issues were addressed across 30 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
Three problems from review on the interactive init path. An explicit --org or --project still opened its picker, and because the resolver compares the answer against the value it was seeded with, accepting the preselected entry cleared the save flag the flag had set. `trace init --project foo` on a repository with no projectName therefore asked a pointless question and then never wrote foo to .chainloop.yml. The identity step now skips the question for a value that came from a flag and leaves its save behavior alone. The first connection was pinned to the organization .chainloop.yml holds, so a repository pinning an organization the user has since left failed the auth check before the picker could offer another one. It now authenticates unpinned, which is enough for listing memberships, and repins once the organization is settled. go.mod was not tidied after adding huh, which left it recorded as an indirect dependency and failed the module tidy check. The interactive steps moved into resolveIdentityInteractively, which takes the listers, the prompter and a repin callback, so the flag and pinning behavior is covered by table tests rather than only by a live control plane. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
|
Thanks — three of the five were real. Fixed in 986b8d8. Explicit flags still opened the picker (P2). Confirmed, and worse than the report suggests. Stale pinned organization failed auth before the picker (P2). Confirmed. The first connection now authenticates unpinned, which is all that listing memberships needs, and repins once the organization is settled. The org resolver already had a test for the "no longer a member" case; the auth check was reaching it first.
Two I did not act on: Root pre-run writing the current organization. Real, but pre-existing and not specific to this command:
🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri |
Open the replacement connection before dropping the old one, so the executor variable always names a live connection. The nil sentinel it used to need on a failed dial, and the caller's matching nil check, both go away. The pinning test asserted a flag that pinTo set itself, so it only restated that pinTo succeeded and would have stayed green had the repinned lister been discarded. It now asserts on the options the project picker was given, which only that lister can supply. Cover every entry of the CI signal list rather than one provider, so adding a signal that does not actually suppress prompting fails the test. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="app/cli/cmd/trace_init_steps_test.go">
<violation number="1" location="app/cli/cmd/trace_init_steps_test.go:144">
P3: The new assertion `assert.Contains(t, f.prompt.selects[1].options, projectAPI)` cannot detect the regression the comment claims ('the project step must use the repinned connection'), because `pinTo` returns the same `f.projects` fake for every org and `fakeProjectLister.ListProjects` returns that same list unconditionally. So the project picker shows `projectAPI` whether it lists against the repinned org or a stale one. To actually verify the org-scoped listing, have `pinTo` return a lister whose projects differ per org (e.g., keyed by the org it was pinned to) and assert the offered options match the chosen org.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
huh's Filtering(true) does not enable filtering, it puts the field straight into filter-input mode: titleView renders the filter box in place of the title, and the arrow keys drive the filter rather than the cursor. The picker came up as a bare "/" above an unhighlighted list, giving no clue what was being chosen or what the question was. Dropping the call restores the title and the cursor; "/" still opens the filter on demand, and the help line says so. Also ask which agents to trace. Providers were reachable only through --claude/--cursor/--opencode, so an interactive run silently got Claude Code and no hint that Cursor and opencode exist. init now offers the registered providers as a multi-select with the default ticked, refusing an empty submission. A provider flag skips the question, the same way --org and --project do, and a non-interactive run keeps today's default. The question is asked before anything is created or written, so an abort still leaves the repository untouched. `trace run` stays flag-driven and resolves its providers through the same flag type. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
Every project uses the same "ai-coding-session" workflow name, so the creation log said nothing about where the workflow landed. Also stop the hook-install comment enumerating each agent's config file: it listed only Claude Code and Cursor, and had already gone stale when opencode was added. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
The assertion claimed the project step runs on the repinned connection, but the fixture handed back one lister for every organization and that lister answered with the same projects regardless, so it held either way. Each organization now owns a distinct project and pinTo returns a fresh lister per pin, so the options the picker was given identify the connection that listed them. Pointing the pin at the wrong organization now fails the test. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
|
Good catch, and correct — fixed in c61326c. The assertion was checking the project picker's options against a fixture that returned one lister for every organization, and that lister answered with the same projects regardless of which one it was pinned to. It would have held whether the project step used the repinned connection or a stale one. The fixture now gives each organization its own projects and returns a fresh lister per pin, so the offered options identify the connection that produced them. I verified it has teeth by mutating Worth noting this was a regression I introduced while fixing the previous version of the same assertion, which checked a flag the fixture set on itself. Same class of mistake twice: asserting on something the test controls rather than on something the code under test decides. The three findings from the earlier review are addressed in 986b8d8, and the two I did not act on are explained in the comment above. 🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri |
The project listing is filtered by what the caller can see, which is a wider set than what they can write to. Picking a project you only view sent init all the way to workflow creation before the server refused it, and the refusal names the project by UUID because the CLI drops its own wrapping for PermissionDenied. The picker was offering a dead end. ProjectService.List now returns can_create_workflow per project and the picker offers only those. The answer is resolved server-side because a caller can hold several roles on the same project, directly and through products, and any one of them granting the permission is enough; a single role field could not say that. Each distinct role is enforced once per listing rather than once per project. Filtering happens after paging, not inside it: counting what survived the filter rather than what the server sent would end the walk on a page of read-only projects and hide everything after it. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 10 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…jects out of the picker The workflow-create capability reported by the project listing ignored the caller's organization role, so an organization viewer, which may list projects but not create workflows, saw every project as writable. It now mirrors both checks the create call faces: the API-level one against the organization role, administrators skipping it as the middleware does, and then the per-project one. The trace init picker filtered read-only projects out of the listing, which made a project the caller can only view indistinguishable from one that does not exist. A project pinned in .chainloop.yml is offered when it is missing from the listing, so a read-only one was offered anyway and preselected. The listing now carries the capability up to the caller, which keeps the two cases apart, and a read-only pinned project is neither offered nor used to seed a new name. Dismissing a prompt also ended trace init with "ERROR aborted" and exit 1 instead of the clean stop it is meant to be. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 11 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Keeping a project the caller can only view out of the picker was not enough: the free-form name prompt accepted anything valid, and a name that matches an existing project resolves to that project rather than creating one. Typing the name of a read-only project therefore reached the same permission error the picker now avoids, and an organization exposing only read-only projects goes straight to that prompt. The new-project validator now knows which projects are read-only and refuses them at the prompt, saying why. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
|
Thanks, do you have a screenshot for this? |
Yes, a video will come |
A project name in .chainloop.yml was offered in the picker even when the organization did not have it, on the assumption that it was visible only through the repository or not created yet. It rendered like any other entry and was preselected, so pressing Enter sent the control plane a project it had never seen. Workflow creation then fell through to creating the project, which fails outright for anyone without that permission, and reported that they cannot create projects when they believed they were picking an existing one. The listing now decides what exists. A pinned name is offered only when the listing carries it and the caller can add a workflow to it. One the listing carries as read-only is refused as before. One the listing does not carry at all is still the name the repository is asking for, so it seeds the new-project prompt rather than posing as an existing project, and a warning says it was not found. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
…e summary line The agent picker hid its last option. huh's multi-select, given no height, sizes its viewport from the options and then subtracts the title's line from that same number, so one fewer option than there are fits. Select does not do this, which is why only this field needs a height. Asking for one line more than the options occupy cancels the subtraction out, and the field construction moved to a function so a test can render it and look for every option. trace init also narrated every step at info level, which buried the result. The per-step lines are now debug and the summary is the one thing printed on a successful run, naming the organization alongside the project, workflow and agents. It reads "repository initialized", since that is what it did. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Initialization ended on a log line about what had happened and nothing about what to do with it. The most important part was invisible: everything init writes lives in the repository, so until those files are committed the tracing applies to one working copy, while committing them onboards the rest of the team without anyone else running init. The command now closes on stdout rather than through the logger, since this is the command's result and not a note about something that happened on the way there. It reports the organization, project, workflow and agents, then the files to commit as a git add line naming each selected agent's configuration, the agents to start, and a link to the guide for the rest. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
"the rest of your team is traced" read as though the people were the subject, and the trailing clause left the sentence hanging. The benefit is now its own sentence, and "these files" covers .chainloop.yml being modified rather than created. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The closing summary, the picker and the command help each said "agent" or "provider" where the user sees them. They are all harnesses now, so the question and the result use the same word. The last step also promised that "AI-assisted commits are attested on push", which describes the mechanism rather than what the user gets. It now says the AI coding sessions are recorded and stored automatically. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Four things it got wrong. It said anyone who pulls the committed files is set up automatically. The git hooks are written to .git/hooks, which git does not carry between clones, so a teammate still has to run init. Committing spares them answering anything, which is what the step now says. It promised the AI coding sessions are stored, but a session whose commits carry no AI attribution is skipped before any attestation is created. Storage is now tied to the commits, which is when it happens. It listed every selected harness even when installing its hooks failed, offering a file that was never written for committing. Only what installed is reported, and a run where nothing installed is an error rather than a congratulation. Its git add line named paths relative to the repository root, but git add resolves its arguments against the working directory, so the command staged nothing when init ran from a subdirectory. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…ooks first Non-interactive init with no organization in .chainloop.yml targets the one the CLI points at, but the summary left the line out entirely. It reports that organization now, which is what the connection used rather than a guess. The check for a run where no harness hooks installed ran after the configuration, the git hooks and the initialized marker had already been written, leaving a repository that looked set up and would record nothing. Installing the harness hooks is now the first thing left behind, so failing there writes none of the rest. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Creating a project is a permission of the organization role, and the contributor role does not carry it. The picker offered "Create a new project…" to everyone, so a contributor who chose it typed a name and was told they are not allowed to create projects, which is the dead end this command exists to remove. It is not derivable from the projects themselves either: a contributor can administer every project it can see and still create none. ProjectService.List now reports it alongside the listing, resolved by the same check the create call enforces, with a test pinning the two together so they cannot drift apart. It sits on the response rather than on the items because it describes the caller, not a project. The picker offers the create entry only when it would work. A caller who can neither write to a visible project nor create one is told so, instead of being asked for a name that would be refused. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 12 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Reporting whether a project can be created added a branch that repeated the picker's own tail, a second place naming the installed harnesses, and a second list built beside the one it duplicated. The permission now reads as a condition on the paths already there rather than as a path of its own. canCreateProject returns its error raw, the way the checks beside it do. Its callers already convert at the boundary, so wrapping it here logged an infrastructure failure twice and masked it once. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
The titles named a setting rather than the choice being made, which reads as a label on a form the user did not open. They now say what pressing enter does, and the project picker has one for each of its two shapes: a caller who cannot create one is only selecting, and a title offering more than the list holds is the same dead end as an entry that cannot be acted on. Creating a project moves under the projects, since picking one of those is the common answer. huh draws every entry alike and has no separator or grouping, so the "+" is what sets an action apart from the names above it. The cursor starts on the first project, and on creating only when the repository points at a project the organization does not have, which is the name it is asking for. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
The shipped contract is looked up by name, and the lookup answered whether the organization has one, not whether this project can attach it. A contract of the user's own carrying that name, scoped to another project, was passed to create, which the control plane refuses: "contract is scoped to a different project", on an init that had nothing to do with that contract. The lookup now returns the contract, and a project-scoped one is used only in the project it belongs to, comparing names the way the control plane does. Falling back to the default contract is right for a name nobody asked for; a contract named with --contract is reported instead, saying which project holds it rather than leaving the server to refuse it. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
Setting a repository up is when .chainloop.yml names a project that has not been created, so reporting it as a problem is noise at exactly the moment it is expected. The next prompt already asks for the name, prefilled with it. Kept at debug, where the rest of the init narration is. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
…ale pin A project name in .chainloop.yml that the organization does not have was proposed back as the name to create. Nothing was ever created under it, so it is as likely to be a typo as an intention, and it arrived prefilled and ready to accept. The repository's directory name is the seed in every case now. The cursor still starts on creating when the pinned project does not exist, since nothing the repository points at can be picked. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 6 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…able Belonging to no organization ended trace init with an error naming another command to go and run. There is nothing to select in that case and every step after it needs an organization, so it now asks for a name and creates it. Only then: with one to pick, picking is the answer. An instance can restrict who may create organizations, which is reported as the ask-to-be-invited it is. An organization the caller can only view cannot hold a trace workflow. It is shown in the picker, marked, and refused when picked, rather than dropped: an organization missing from the list reads as something gone wrong, and the marker says what to do about it. Selecting one keeps the prompt open, so the picker now takes a validator the way the input already did. Organizations are named by the rule projects are, so the name check is shared, with the subject passed in for the message. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
…bove "using your only organization" was printed as a log line over the project picker, which read as something that had happened rather than as part of what was being asked. It is context the question needs: the projects on offer come from that organization, and a new one would be created in it. Both prompts now carry it under their title, inside the same frame huh draws the question in, and the log line is gone. The organization picker itself needs none: it is the question. Asking for a first organization says why there, too. Two things had to be worked around. Accessible mode draws a title and options and no description at all, so the context is folded into the title there, where it would otherwise be lost to a screen reader. And an input's DescriptionFunc is not evaluated for the first draw, only once its binding changes, so the field sets both and opens with the context rather than an empty line. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
…led init Creating a project was reported as allowed for anyone RBAC does not narrow, which is the administrators and the organization viewer alike. A viewer can read, so the API-level check refuses it the workflow that follows, and the listing was offering an option that could only fail. Only the administrators answer yes there now; an API token outside RBAC still acts for its organization. The test pinning the reported answer to the enforced one is gone. Extracting the reported answer out of the enforcing call made the two the same code, so the test compared a function to itself and would have passed whatever it did. What is worth pinning is agreement with the create RPC as the middleware reaches it, which is the service-level test tracked in #3419. Installing the harness hooks first means a later failure left them behind, calling a repository that was never set up. The rest of the setup is one step now, and its failure takes the hooks back out again. The organization on the project prompts reads as a sentence rather than a label and a value, which looked like one more row above the options it sits over. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
Moving it into the project prompt put it between the title and the options, where it read as one more row of the list rather than as context. It goes back above the question, where it does not compete with the options for attention. The description the prompts carry stays: asking for a first organization still says why it is asking, which is a question with nothing to pick behind it. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Undoing a failed init by uninstalling the hooks stripped a repository that was already set up: installing over hooks that are present changes nothing and still counts as installed, so a second run that then failed locally would take the working hooks of the first one with it. The configuration file is recorded before it is written to and put back as it was, with its mode. A file this run created is removed instead, and the directory that came with it when nothing else moved in. The test covering the state step asserted on a path the step never writes, so it passed whatever happened. It now looks under the git directory, where the state actually lives, and fails the save in a way that does not depend on which user runs it. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Three gaps in the rollback, each one a way for a failed run to leave the repository changed. A harness whose hooks failed to install part-way through kept whatever the write got to, since only a successful install was recorded to undo. It is put back before the next harness is tried. .chainloop.yml was not covered at all. The step that writes it stops at the first thing that fails, so a failure after the save left the configuration behind with nothing recording into it. It is recorded alongside the harness files and put back with them. Removing the directory a created file sat in took the immediate parent whether or not this run created it, and left the ones above it. The closest directory that was already there is recorded, and only what was created below it is removed. opencode keeps its hook two levels down, so both halves of that mattered. The test for the state step failed on the first thing that step does, which made "leaves nothing" true however the step behaved. It fails on the trace directory now, after the configuration has been written, which is the case the snapshot exists for. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
93c6253
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Asking someone to pick from a list or type a value has nothing to do with tracing, and neither do the two huh quirks worked around here: a field that hides its last option unless told its own height, and an accessible mode that draws no description at all. app/cli/pkg/prompt takes all of it, along with the aborted error, which is what a dismissed question returns. What stays behind is the one part that is Chainloop's rather than the terminal's: a name typed freely is stored as a DNS-1123 label, so the input shows what it will become. The package takes that as a callback and knows nothing about naming. Exercising it on its own found a third quirk of the same family: an input given no validator panics, the way a select does. The trace prompts always passed one, so it had never come up. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
The constructor in cmd was the last thing there still named after the terminal library, now that huh is hidden behind pkg/prompt; it is named after the type it returns instead. In the package itself, the environment scan is a single ContainsFunc rather than a flag and a break, and run reports its error once instead of on two paths. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 184822bb-e811-42fd-8e13-735227edc5ce
Closes #3426. Closes PFM-7114.
https://www.tella.tv/video/vid_cmtul6y87001j0agmbli4hat5/view
chainloop trace initfailed on--project is required. That was a new user's first experience of tracing: an error naming a flag, with no way to learn what a project is, which ones they could use, or which organization any of it would land in. Finding out meantchainloop project list, which only ships in the enterprise CLI, or guessing a name and learning the rules from a rejected API call.Everything needed to avoid that is already known once the user is logged in. Now init asks.
What it does
Run
chainloop trace initon a terminal and it walks through the setup:Each answer defaults to what
.chainloop.ymlalready holds, so pressing Enter on a configured repository keeps it. Flags still win:--org,--projectand--claude/--cursor/--opencodeskip their question. CI and redirected output ask nothing, where--projectstays required.The rule throughout is that nothing is offered unless it will work. A project you can only view is not in the list, and neither is one that
.chainloop.ymlnames but the organization does not have; "create a project" appears only if your role allows it; an organization you can only view is shown marked, and says who to ask. Every question is answered before anything is created or written, so an abandoned or failed run leaves the repository as it was.A successful run ends with what was set up, the files to commit, and what to do next.
Server side
The picker needs a project listing the control plane did not have, so this adds
ProjectService.List: organization-scoped, RBAC-filtered, paginated. No schema change, no migration.It also reports two permissions, because seeing a project is not the same as being able to use it, and neither is derivable client-side:
can_create_workflow, per project — a viewer can read a project but not add a workflow, and a caller can hold several roles on one project.can_create_project, per response — it describes the organization role, not any project: a contributor can administer every project it sees and still create none.Both come from the same checks the write calls enforce, so the picker only offers what will succeed.
Notes
The prompts live in a new
app/cli/pkg/promptpackage: select, multi-select and input overcharm.land/huh/v2, with an accessible plain-text mode for screen readers and terminals that cannot draw the UI. It knows nothing about tracing, so other commands can ask questions through it. Thetrace initlogic talks to it through a small interface and is tested against a scripted fake, without a terminal.huhis a new dependency and adds about 2.3 MB to the binary.A service-level RBAC test for the new RPC is tracked in #3419.
The guide says committing
.chainloop.ymland the agent config onboards the rest of the team automatically. It does not: the git hooks live in.git/hooks, which git does not carry between clones, so a teammate still runstrace init. The CLI says what the code does; the docs page may want the same correction.AI disclosure: this change was produced with the assistance of Claude Code. Every commit carries an
Assisted-by: Claude Codetrailer.🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri