Skip to content

feat(cli): ask for the organization and project on trace init - #3418

Merged
migmartri merged 30 commits into
mainfrom
PFM-7114-trace-init-interactive-org-project
Sep 10, 2026
Merged

migmartri merged 30 commits into
mainfrom
PFM-7114-trace-init-interactive-org-project

Conversation

@migmartri

@migmartri migmartri commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Closes #3426. Closes PFM-7114.

https://www.tella.tv/video/vid_cmtul6y87001j0agmbli4hat5/view

chainloop trace init failed 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 meant chainloop 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 init on a terminal and it walks through the setup:

  • Organization — picked from the ones you belong to, created for you if you belong to none.
  • Project — picked from the ones you can actually add a workflow to, or named freely and normalized to the label Chainloop stores.
  • Harnesses — which AI tools to trace.

Each answer defaults to what .chainloop.yml already holds, so pressing Enter on a configured repository keeps it. Flags still win: --org, --project and --claude/--cursor/--opencode skip their question. CI and redirected output ask nothing, where --project stays 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.yml names 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/prompt package: select, multi-select and input over charm.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. The trace init logic talks to it through a small interface and is tested against a scripted fake, without a terminal. huh is 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.yml and 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 runs trace 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 Code trailer.

🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri

`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
@chainloop-platform

chainloop-platform Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

AI Session Checks — 🟡 79% · ⚠️ 1 failing

Avg score Sessions Failing policies Attribution Files Lines Total Duration
🟡 79% 1 ⚠️ 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)

View session details ↗

Change Summary

  • Adds interactive trace init organization and project selection with .chainloop.yml defaults and CHAINLOOP_NO_PROMPT.
  • Adds slugification, validation, huh-based prompting, and client-side project paging helpers plus tests.
  • Adds control-plane ProjectService.List with 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.

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

Status Attribution File Lines
created ai app/cli/cmd/trace_init_identity_test.go +475 / -0
created ai app/cli/cmd/trace_init_identity.go +236 / -0
modified ai app/cli/cmd/trace_init_resolve.go +177 / -22
created ai app/controlplane/pkg/biz/project_list_integration_test.go +190 / -0
created ai app/cli/cmd/trace_init_steps_test.go +167 / -0
modified ai app/cli/internal/trace/config/slug_test.go +120 / -6
created ai app/cli/pkg/action/trace_identity_test.go +120 / -0
created ai app/cli/cmd/trace_init_prompt.go +119 / -0
created ai app/cli/pkg/action/trace_identity.go +98 / -0
created ai app/cli/internal/trace/config/slug.go +79 / -0
created ai app/cli/cmd/trace_init_prompt_test.go +76 / -0
modified ai app/controlplane/internal/service/project.go +58 / -0
modified ai app/controlplane/pkg/data/project.go +49 / -5
modified ai go.sum +52 / -0
modified ai app/cli/cmd/trace_init.go +25 / -17
modified ai app/controlplane/pkg/biz/project.go +34 / -0
modified ai app/controlplane/api/controlplane/v1/project.proto +32 / -0
modified ai go.mod +21 / -1
modified ai app/cli/cmd/trace_init_test.go +8 / -3
modified ai app/cli/documentation/cli-reference.mdx +9 / -0
modified ai app/controlplane/pkg/authz/authz.go +7 / -0
modified ai app/cli/cmd/root.go +6 / -0

Policies (4, 1 failing)

Status Policy Material Messages
✅ Passed ai-config-ai-agents-allowed ai-coding-session-184822 -
✅ Passed ai-config-no-dangerous-commands ai-coding-session-184822 -
⚠️ Failed ai-config-no-secrets ai-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-allowed ai-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

View attestation ↗


Powered by Chainloop and Chainloop Trace

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go
Comment thread app/cli/cmd/trace_init_prompt.go Outdated
Comment thread app/cli/cmd/trace_init.go
Comment thread app/cli/cmd/trace_init_resolve.go Outdated
Comment thread go.mod Outdated
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
@migmartri

Copy link
Copy Markdown
Member Author

Thanks — three of the five were real. Fixed in 986b8d8.

Explicit flags still opened the picker (P2). Confirmed, and worse than the report suggests. resolveIdentityInteractively now skips a question whose value came from a flag and leaves its save flag alone; covered by table tests.

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.

huh recorded as indirect (P3). Correct, and the cause of the four failing Test jobs — they all run the module tidy check. go mod tidy run.

Two I did not act on:

Root pre-run writing the current organization. Real, but pre-existing and not specific to this command: PersistentPreRunE writes the resolved organization to the CLI config for every command when local config has none. This PR's guarantee is that nothing is written to the repository before the workflow exists, which still holds. Deferring that write for one command is a change to shared startup behavior and belongs in its own PR.

errAborted exiting 1. Intentional. A dismissed prompt is a cancellation, not a success, and the spec called for a non-zero exit. The ERROR aborted prefix is noisier than it needs to be — happy to quiet that if reviewers prefer, but the exit code should stay non-zero.

🤖 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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init_steps_test.go Outdated
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
@migmartri

Copy link
Copy Markdown
Member Author

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 pinTo(cfg.organization) to pinTo("") — the test fails, and passes again when reverted.

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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/controlplane/internal/service/project.go
Comment thread app/cli/pkg/action/trace_identity.go Outdated
Comment thread app/cli/pkg/action/trace_identity.go Outdated
…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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init_identity.go Outdated
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
@jiparis

jiparis commented Sep 9, 2026

Copy link
Copy Markdown
Member

Thanks, do you have a screenshot for this?

@migmartri

Copy link
Copy Markdown
Member Author

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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go Outdated
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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go
Comment thread app/cli/cmd/trace_init.go Outdated
Comment thread app/cli/cmd/trace_init.go Outdated
Comment thread app/cli/cmd/trace_init_test.go Outdated
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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go Outdated
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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go
…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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go
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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/pkg/action/trace_identity.go
Comment thread app/controlplane/internal/service/project_permissions_test.go Outdated
Comment thread app/controlplane/internal/service/service.go Outdated
@migmartri
migmartri requested a review from a team September 9, 2026 20:16
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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/pkg/action/trace_workflow.go
Comment thread app/cli/cmd/trace_init_identity.go
…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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go Outdated
Comment thread app/controlplane/internal/service/project_permissions_test.go
Comment thread app/controlplane/internal/service/service.go
Comment thread app/cli/cmd/trace_init_test.go Outdated
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

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go Outdated
Comment thread app/cli/cmd/trace_init.go
Comment thread app/cli/cmd/trace_init_test.go Outdated
jiparis
jiparis previously approved these changes Sep 9, 2026
Comment thread app/cli/cmd/trace_init_prompt.go
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
@migmartri
migmartri dismissed stale reviews from matiasinsaurralde and jiparis via 93c6253 September 10, 2026 08:54

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment thread app/cli/cmd/trace_init.go
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

@jiparis jiparis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like it!

@migmartri
migmartri merged commit f551118 into main Sep 10, 2026
16 of 17 checks passed
@migmartri
migmartri deleted the PFM-7114-trace-init-interactive-org-project branch September 10, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chainloop trace init fails with "--project is required" instead of asking

3 participants