Skip to content

fix(session-context): add --root flag for opencode's true parent session#444

Closed
rhuanbarreto wants to merge 1 commit into
mainfrom
fix/opencode-session-context-root
Closed

fix(session-context): add --root flag for opencode's true parent session#444
rhuanbarreto wants to merge 1 commit into
mainfrom
fix/opencode-session-context-root

Conversation

@rhuanbarreto

@rhuanbarreto rhuanbarreto commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • archgate session-context opencode --skip 1 guessed "the parent session" by recency — the second most-recently-updated session sharing a project directory. Verified against a real opencode database that this is wrong in two ways specific to opencode: the Skill tool runs inline in the current session (creates no new session row to skip past), and sibling sub-agent sessions fanned out from the same parent interleave with it in recency order.
  • Added a new --root flag to archgate session-context opencode that resolves the true top-level session directly via opencode's session.parent_id column, instead of guessing by recency. Correct regardless of nesting depth or sibling fan-out.
  • Updated the CLI reference docs (EN, pt-br, nb) and regenerated docs/public/llms-full.txt.
  • Companion fix: the distributed opencode lessons-learned skill now uses --root instead of --skip 1.

Why

Live-reproduced against the user's real opencode database: a archgate-lessons-learned skill run's archgate session-context opencode --skip 1 --max-entries 60 call returned the "General process ADR review" sub-agent's private transcript instead of the actual parent development session — because 4 sibling sub-agents had fanned out from that parent, and --skip 1 landed on a sibling instead of the parent.

Known follow-up (not fixed here): the same class of bug likely affects Claude Code and Cursor's --skip 1 guidance too (live-reproduced for Claude Code during this session), since their Skill tool also runs inline. They have no parent_id-equivalent, so --root doesn't directly transfer — a different fix would be needed. Captured in agent memory for a future session.

Test plan

  • bun run validate — lint, typecheck, format, 1318 tests, ADR check (39/39), knip, build check all pass
  • New tests cover: --root resolving the parent through sibling fan-out, --root + --skip combined, and the no-root-sessions-available error path
  • @reviewer skill — APPROVED (0 violations, 1 warning investigated and dismissed as a pre-existing, unrelated pattern)
  • @lessons-learned skill — captured project memory, no new ADR needed (self-contained bug fix)

https://claude.ai/code/session_01LUiXGugDB2g3WJXbtzzjVo

`archgate session-context opencode --skip 1` guessed the parent session
by recency (the second most-recently-updated session sharing a project
directory). That heuristic breaks in two ways specific to opencode:

- The `Skill` tool executes inline in the current session — it creates
  no new session row, so "skip past my own session" instead skips past
  the actual parent.
- Sibling sub-agent sessions fanned out from the same parent (e.g. the
  archgate:reviewer skill's parallel domain reviews) interleave with the
  parent in recency order, so `--skip 1` can land on any sibling.

Verified against a real opencode database: a live archgate-lessons-learned
run's `--skip 1` call returned the "General process ADR review"
sub-agent's private transcript instead of the parent conversation.

`--root` resolves the true top-level session directly via opencode's
`session.parent_id` column instead of guessing by recency, and is
correct regardless of nesting depth or sibling count.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a --root flag to the archgate session-context opencode command, allowing selection of the true top-level session by filtering candidates to those with parent_id IS NULL before applying --skip. Changes span the helper's session-selection logic and SQL query, CLI option definition and description updates, new and updated tests, documentation updates across English, Norwegian, and Portuguese locales plus llms-full.txt, and new agent-memory markdown files documenting the bug and fix context.

Changes

Cohort / File(s) Change Summary
src/helpers/session-context-opencode.ts Adds root?: boolean option, includes parent_id in query, filters candidates to root sessions, updates error messages
src/commands/session-context/opencode.ts Adds --root CLI option, updates --skip description, forwards opts.root
tests/commands/session-context/opencode.test.ts, tests/helpers/session-context-opencode-root.test.ts Adds/updates tests covering --root option presence and behavior
docs/public/llms-full.txt, docs/src/content/docs/**/session-context.mdx Updates docs for directory-based matching, --root flag, and new examples
.claude/agent-memory/archgate-developer/*.md Adds memory notes on generated skill files and the skip-root fix

Sequence Diagram(s)

sequenceDiagram
    participant CLI as opencode.ts
    participant Helper as readOpencodeSession
    participant DB as SQLite session table

    CLI->>Helper: readOpencodeSession(projectRoot, { root: opts.root, skip })
    Helper->>DB: query sessions incl. parent_id
    DB-->>Helper: session rows
    alt root is true
        Helper->>Helper: filter candidates to parent_id IS NULL
    end
    Helper->>Helper: select target by sessionId or skip index
    alt target found
        Helper-->>CLI: session transcript
    else target missing
        Helper-->>CLI: error with scoped available list
    end
Loading

Related Issues: None referenced.

Related PRs: None referenced.

Suggested labels: documentation, enhancement, cli

Suggested reviewers: archgate-developer

🎀 A session that hides among its siblings,
Now finds its root, no more it dithers,
A flag called --root cuts through the fog,
Skipping true parents, not a rogue subagent's log,
Docs and tests align in tidy rows.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an opencode --root flag for true parent session lookup.
Description check ✅ Passed The description is directly about the same opencode --root fix, related docs, tests, and the sibling plugin companion change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7495901. Configure here.

const target = options?.sessionId
? matching.find((s) => s.id === options.sessionId)
: matching[skip];
: candidates[skip];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong root with multiple conversations

Medium Severity

With --root (default --skip 0), selection uses the most recently updated row among all parent_id IS NULL sessions for the project, not the root of the active session tree. When two or more top-level sessions share the directory and the latest activity is a child of an older root, --root can return a different root’s transcript than the sub-agent or inline skill intended.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7495901. Configure here.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7495901
Status: ✅  Deploy successful!
Preview URL: https://825ba74f.archgate-cli.pages.dev
Branch Preview URL: https://fix-opencode-session-context-ra7h.archgate-cli.pages.dev

View logs

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 90.0% (6946 / 7719)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 88.1% 1879 / 2133
src/engine/ 90.9% 1463 / 1610
src/formats/ 100.0% 142 / 142
src/helpers/ 90.3% 3462 / 3834

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/helpers/session-context-opencode.ts`:
- Around line 127-154: The `available` result in
`src/helpers/session-context-opencode.ts` is using the wrong scope when
`options.sessionId` is set alongside `root`. Update the failure path in the
session selection logic so the `available` list matches the actual lookup set
used by the `target` resolution in that branch (`matching` for `sessionId`,
`candidates` for skip-based selection), and keep the scope label/error text
aligned with that choice in the `rootOnly`/`skip` handling.

In `@tests/helpers/session-context-opencode-root.test.ts`:
- Around line 109-126: The control assertion in readOpencodeSession test is
being skipped if skipped.ok is false, so the “before” case can silently no-op
and still let the test pass. Update the test case in test("root reads the true
parent even when sibling sub-agents are more recent") to assert skipped.ok
before checking skipped.data.sessionId, using the existing readOpencodeSession
result variable so the precondition is always enforced.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2ce22edf-0236-4c4d-99c4-9d286da997d8

📥 Commits

Reviewing files that changed from the base of the PR and between a53b305 and 7495901.

📒 Files selected for processing (11)
  • .claude/agent-memory/archgate-developer/MEMORY.md
  • .claude/agent-memory/archgate-developer/project_plugins_generated_skill_files.md
  • .claude/agent-memory/archgate-developer/project_session_context_skip_root_fix.md
  • docs/public/llms-full.txt
  • docs/src/content/docs/nb/reference/cli/session-context.mdx
  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
  • docs/src/content/docs/reference/cli/session-context.mdx
  • src/commands/session-context/opencode.ts
  • src/helpers/session-context-opencode.ts
  • tests/commands/session-context/opencode.test.ts
  • tests/helpers/session-context-opencode-root.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Smoke Test (Windows) / Windows
  • GitHub Check: Lint, Test & Check
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Cloudflare Pages
⚠️ CI failures not shown inline (2)

GitHub Actions: DCO / DCO Sign-off Check: fix(session-context): add --root flag for opencode's true parent session

Conclusion: failure

View job details

##[group]Run base="a53b30507a139db915271095a5a29ccefbd7cecf"
 �[36;1mbase="a53b30507a139db915271095a5a29ccefbd7cecf"�[0m
 �[36;1mhead="749590188c8a687339fdf67e366565cf266710f2"�[0m
 �[36;1mfailed=0�[0m
 �[36;1m�[0m
 �[36;1mfor sha in $(git rev-list --no-merges "$base".."$head"); do�[0m
 �[36;1m  if ! git log -1 --format='%B' "$sha" | grep -qiE '^Signed-off-by: .+ <.+>'; then�[0m
 �[36;1m    echo "::error::Commit $sha is missing a DCO Signed-off-by line."�[0m

GitHub Actions: DCO / 0_DCO Sign-off Check.txt: fix(session-context): add --root flag for opencode's true parent session

Conclusion: failure

View job details

##[group]Run base="a53b30507a139db915271095a5a29ccefbd7cecf"
 �[36;1mbase="a53b30507a139db915271095a5a29ccefbd7cecf"�[0m
 �[36;1mhead="749590188c8a687339fdf67e366565cf266710f2"�[0m
 �[36;1mfailed=0�[0m
 �[36;1m�[0m
 �[36;1mfor sha in $(git rev-list --no-merges "$base".."$head"); do�[0m
 �[36;1m  if ! git log -1 --format='%B' "$sha" | grep -qiE '^Signed-off-by: .+ <.+>'; then�[0m
 �[36;1m    echo "::error::Commit $sha is missing a DCO Signed-off-by line."�[0m
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript strict mode, ESNext target, and ES modules throughout
Use safeParse() when validating with Zod schemas
Use minimal dependencies; prefer Bun built-ins per ARCH-006

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • src/commands/session-context/opencode.ts
  • tests/commands/session-context/opencode.test.ts
  • src/helpers/session-context-opencode.ts
tests/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Test files must mirror src/ directory structure with fixtures in tests/fixtures/

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • tests/commands/session-context/opencode.test.ts
**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Runtime must use Bun (>=1.2.21), not Node.js compatible

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • src/commands/session-context/opencode.ts
  • tests/commands/session-context/opencode.test.ts
  • src/helpers/session-context-opencode.ts
tests/**/*.test.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-005-testing-standards.md)

tests/**/*.test.ts: Use Bun's built-in test runner (bun:test) for all test files, and place tests under tests/ mirroring the src/ directory structure with <module-name>.test.ts naming.
Use temp directories (mkdtemp) for filesystem tests, and clean them up in afterEach or afterAll.
Close external SDK instances (servers, clients, transports, connections) in afterEach or afterAll, managing their lifecycle in hooks rather than inside individual test bodies.
When a test creates a temporary git repository and runs git commit, configure local user.email and user.name immediately after git init.
Test public module interfaces, not private implementation details.
Use descriptive test names that explain the expected behavior.
Every runnable test()/it() must contain at least one expect() assertion; smoke tests must make the contract explicit with expect(() => fn()).not.toThrow() or await expect(promise).resolves.toBeUndefined().
Use test.skip, test.skipIf, or test.todo for intentionally empty or disabled tests; do not use bare return or empty callbacks to skip work.
If the first expect() is being added to a previously assertion-less test file, add expect to the bun:test import.
When mocking fetch in tests, assign directly to globalThis.fetch and restore the original or use mock.restore() afterward.
Wrap spyOn() and inline mockImplementation() usage in try/finally, or create and restore spies in hooks, so mockRestore() always runs.
Only raise a per-test timeout above the global bun test --timeout 60000; never set a shorter per-test timeout.
Mock first-party modules with import * as mod plus spyOn(mod, "fn"), not mock.module().
When a test needs to redirect user-scope paths, mock os.homedir() instead of relying on HOME/Bun.env.HOME; restore the spy in test hooks.
Do not depend on network access in unit tests.
Do not leave temp files after test runs.
Do not leave external SDK instances open after tests...

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • tests/commands/session-context/opencode.test.ts
**/*.{ts,tsx,js,jsx,mjs,cjs}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-006-dependency-policy.md)

**/*.{ts,tsx,js,jsx,mjs,cjs}: Use Bun built-ins for file I/O (Bun.file, Bun.write), HTTP, subprocess execution (Bun.spawn), globbing (Bun.Glob), and testing (bun:test).
Do not use Node.js-specific APIs when Bun alternatives exist; for example, use Bun.file() instead of fs.readFile() for simple reads.
Prefer node: built-in modules such as node:util, node:path, and node:fs over npm alternatives.
Do not use utility libraries for single functions, such as importing lodash for one helper like pick.
Do not use path aliases (tsconfig paths); use relative imports with Bun's native module resolution.
Do not install packages globally during development; use bunx for one-off tools.

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • src/commands/session-context/opencode.ts
  • tests/commands/session-context/opencode.test.ts
  • src/helpers/session-context-opencode.ts
tests/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)

In test files, use _resetPlatformCache() to simulate different platforms instead of mocking or mutating process.platform directly.

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • tests/commands/session-context/opencode.test.ts
{src,tests}/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/LEGAL-001-spdx-license-headers.md)

{src,tests}/**/*.ts: Every TypeScript source file in src/ and tests/ must begin with // SPDX-License-Identifier: Apache-2.0 followed by // Copyright 2026 Archgate.
If a TypeScript file has a shebang line (for example #!/usr/bin/env bun in src/cli.ts), the SPDX license header must appear immediately after the shebang.
Use single-line // comments for the SPDX header; do not use block comments (/* */) or alternate license identifiers.

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • src/commands/session-context/opencode.ts
  • tests/commands/session-context/opencode.test.ts
  • src/helpers/session-context-opencode.ts
**

⚙️ CodeRabbit configuration file

**: # CLAUDE.md

Archgate is a CLI tool for AI governance via Architecture Decision Records (ADRs) — combining human-readable docs with machine-checkable rules. The CLI dogfoods itself via ADRs in .archgate/adrs/. AI features are delivered as a Claude Code plugin (../plugins/claude-code), not via direct API calls.

Technology Stack

  • Runtime: Bun (>=1.2.21) — not Node.js compatible
  • Language: TypeScript (strict mode, ESNext, ES modules)
  • CLI framework: Commander.js (@commander-js/extra-typings)
  • Linter: Oxlint | Formatter: Oxfmt | Dead exports: Knip | Commits: Conventional Commits

Commands

bun run src/cli.ts <command>  # run CLI locally
bun run lint                  # oxlint
bun run typecheck             # tsc --build
bun run format                # oxfmt --write
bun run format:check          # oxfmt --check
bun run test                  # all tests (not bare `bun test` — picks up --timeout; see GEN-003)
bun run knip                  # dead export detection
bun run validate              # MANDATORY: lint + typecheck + format + test + ADR check + knip + build check
bun run build:check            # verify build compiles (CI builds binaries via release workflow)
bun run commit                # conventional commit wizard

Validation Gate

bun run validate must pass before any task is considered complete. Fail-fast pipeline: lint → typecheck → format → test → ADR check → knip → build check. Mirrors CI in .github/workflows/code-pull-request.yml.

Git Hooks (Git 2.54+)

Config-based hooks in .githooks run validation locally before commits and pushes:

  • pre-commit: lint + typecheck + format:check (~15s)
  • pre-push: full bun run validate (~60s, mirrors CI)

Activate once per clone:

git config --local include.path ../.githooks

Opt out of a specific hook: git config --local hook.<name>.enabled false. Skip all hooks for a single commit: git commit --no-verify.

GitHub Act...

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • docs/src/content/docs/nb/reference/cli/session-context.mdx
  • src/commands/session-context/opencode.ts
  • tests/commands/session-context/opencode.test.ts
  • docs/public/llms-full.txt
  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
  • src/helpers/session-context-opencode.ts
  • docs/src/content/docs/reference/cli/session-context.mdx

⚙️ CodeRabbit configuration file

**: This project uses Archgate — an AI governance framework based on
Architecture Decision Records (ADRs). The ADRs in .archgate/adrs/
are the authoritative rules for this codebase. Each ADR has a companion
.rules.ts file with automated checks that run via archgate check.

When reviewing, you must:

  1. Treat ADR violations as blocking issues, not suggestions.
  2. Cite the specific ADR ID when flagging a violation (e.g., "Violates ARCH-006").
  3. Focus on semantic and contextual violations that automated rules cannot catch —
    the .rules.ts files already cover syntactic/structural patterns.
  4. If you are unsure whether something violates an ADR, flag it as a question
    rather than approving it.

Files:

  • tests/helpers/session-context-opencode-root.test.ts
  • docs/src/content/docs/nb/reference/cli/session-context.mdx
  • src/commands/session-context/opencode.ts
  • tests/commands/session-context/opencode.test.ts
  • docs/public/llms-full.txt
  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
  • src/helpers/session-context-opencode.ts
  • docs/src/content/docs/reference/cli/session-context.mdx
docs/src/content/docs/**/*.mdx

📄 CodeRabbit inference engine (.archgate/adrs/GEN-001-documentation-site.md)

docs/src/content/docs/**/*.mdx: Use MDX (.mdx) for all content pages under docs/src/content/docs/.
Escape literal curly braces in MDX when showing template syntax; do not use bare {} in prose or code-fence labels.

Files:

  • docs/src/content/docs/nb/reference/cli/session-context.mdx
  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
  • docs/src/content/docs/reference/cli/session-context.mdx
{docs/src/content/docs/**/*.mdx,docs/astro.config.mjs}

📄 CodeRabbit inference engine (.archgate/adrs/GEN-001-documentation-site.md)

When adding a new documentation page, create the MDX file in docs/src/content/docs/<category>/<slug>.mdx and add the page to docs/astro.config.mjs sidebar configuration.

Files:

  • docs/src/content/docs/nb/reference/cli/session-context.mdx
  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
  • docs/src/content/docs/reference/cli/session-context.mdx
docs/src/content/docs/**

📄 CodeRabbit inference engine (.archgate/adrs/GEN-001-documentation-site.md)

Do not create documentation content files outside docs/src/content/docs/; Starlight content must live in that exact directory structure.

Files:

  • docs/src/content/docs/nb/reference/cli/session-context.mdx
  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
  • docs/src/content/docs/reference/cli/session-context.mdx
docs/src/content/docs/**/*.{mdx,md}

📄 CodeRabbit inference engine (.archgate/adrs/GEN-002-docs-i18n.md)

docs/src/content/docs/**/*.{mdx,md}: For every English docs page under docs/src/content/docs/, create a translated file in each locale directory with the exact same relative path and filename, and ensure every locale file corresponds to an existing root file (no orphan translations).
When adding or modifying English documentation content, update the corresponding locale files in the same pull request.
Translate all user-facing prose in docs pages, including titles, descriptions, headings, paragraphs, list items, table text, and admonition content.
Translate user-visible text props in Starlight components such as <Card title="..."> and <LinkCard description="...">.
Keep code blocks, CLI commands, file paths, TypeScript identifiers, technical terms, import statements, component names, and link/href/slug attribute values in English.
Keep internal links unchanged and do not add locale prefixes (for example, use /guides/... rather than /pt-br/guides/...).
Preserve MDX curly-brace escaping (\{\}) in translated content.
Preserve Starlight component import statements identically in translated files.

Files:

  • docs/src/content/docs/nb/reference/cli/session-context.mdx
  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
  • docs/src/content/docs/reference/cli/session-context.mdx
docs/src/content/docs/nb/**/*.{mdx,md}

📄 CodeRabbit inference engine (.archgate/adrs/GEN-002-docs-i18n.md)

Use Norwegian Bokmål (not Nynorsk) for Norwegian translations, with the informal du form and correct Norwegian characters (æ, ø, å).

Files:

  • docs/src/content/docs/nb/reference/cli/session-context.mdx
src/commands/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

src/commands/**/*.ts: Commands must export register*Command(program) function that handles I/O only, with no business logic
Use styleText() from node:util for output formatting; support --json flag for machine-readable output and auto-compact JSON in non-TTY/non-CI contexts
Exit with code 0 for success, 1 for violations, 2 for internal errors, and 130 for user cancellation (SIGINT)

src/commands/**/*.ts: Each command module under src/commands/ must export a register*Command(program) function, with one command per file (or one command group via index.ts).
Command files must be thin: they should only parse arguments, call engine/helpers, and format output; business logic must live in src/engine/, src/helpers/, or src/formats/.
Command modules must not use executableDir() for command discovery, must not call .parse() themselves, and must not spawn child processes for subcommand execution.
Subcommand groups should live in nested directories with an index.ts that composes child commands (for example, src/commands/adr/index.ts).

src/commands/**/*.ts: In command files under src/commands/**/*.ts, options that need type narrowing beyond plain strings MUST use new Option() with .addOption() instead of plain .option().
In src/commands/**/*.ts, import Option from @commander-js/extra-typings alongside Command so typed options get full type inference.
In src/commands/**/*.ts, enum-like options with a fixed set of allowed values must use .choices() on an Option to provide runtime validation and compile-time type narrowing.
In src/commands/**/*.ts, options that require type conversion must use .argParser() on an Option object, not a parser function passed as the third argument to .option().
In src/commands/**/*.ts, register typed options with .addOption() rather than .option().
In src/commands/**/*.ts, pass both the choices array and default values with as const when using .choices() and .default() to pr...

Files:

  • src/commands/session-context/opencode.ts
src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

AI features must be delivered as a Claude Code plugin (../plugins/claude-code), not via direct API calls

src/**/*.ts: Use logError() from src/helpers/log.ts for user-facing errors instead of printing errors directly.
Exit with code 1 for expected failures such as missing config, invalid input, or validation violations.
Let unexpected errors crash naturally so they are treated as internal errors with exit code 2.
Include actionable suggestions in user-facing error messages whenever possible.
Write user-facing errors to stderr via logError(); do not send them to stdout.
When findProjectRoot() returns null for commands that do not require .archgate/, fall back to process.cwd() as the path key or working directory.
Catch ExitPromptError from Inquirer in the top-level error boundary and treat it as user cancellation: exit with code 130 without logging an error or sending it to Sentry.
Do not use console.error() directly; use logError() for consistent formatting.
Do not exit with codes other than 0, 1, 2, or 130.
Do not send user-cancellation errors such as Inquirer ExitPromptError to Sentry; filter them in beforeSend.

src/**/*.ts: Use styleText(format, text) from node:util for all terminal colors and formatting in CLI source files; do not use raw ANSI escape codes or third-party color libraries.
Commands that produce structured results and support --json must emit machine-readable JSON to stdout with no colors or decorative formatting.
When a command supports --json, use formatJSON() from src/helpers/output.ts for JSON serialization, and pass forcePretty: true when the user explicitly provided --json.
Use isAgentContext() from src/helpers/output.ts to enable auto-JSON behavior for commands that support both human-readable and JSON output modes.
CLI output must not include emoji; use text symbols and colors instead.
Send normal command output to stdout with console.log(), and send errors, warnings, and de...

Files:

  • src/commands/session-context/opencode.ts
  • src/helpers/session-context-opencode.ts
src/**/!(*platform).ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-009-platform-detection-helper.md)

src/**/!(*platform).ts: In src/ TypeScript source files, do not read process.platform directly; use src/helpers/platform.ts (isWindows(), isMacOS(), isLinux(), isWSL(), getPlatformInfo()) for all platform detection.
Use the centralized platform helper instead of duplicating OS/WSL detection logic inline anywhere in src/ TypeScript source.
When behavior differs between Linux and Windows, account for WSL by using isWSL() rather than assuming `

Files:

  • src/commands/session-context/opencode.ts
  • src/helpers/session-context-opencode.ts
docs/src/content/docs/pt-br/**/*.{mdx,md}

📄 CodeRabbit inference engine (.archgate/adrs/GEN-002-docs-i18n.md)

Use correct Portuguese diacritical marks in Brazilian Portuguese translations; never write unaccented Portuguese.

Files:

  • docs/src/content/docs/pt-br/reference/cli/session-context.mdx
src/{helpers,engine}/**/*.ts

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-002-error-handling.md)

Do not use console.log(), console.warn(), or console.info() directly in helper or engine files; use logInfo() or logWarn() instead. Command files are exempt because they are the I/O layer.

Files:

  • src/helpers/session-context-opencode.ts
docs/src/content/docs/reference/cli/!(index).mdx

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

Every top-level CLI command must have a corresponding reference page at docs/src/content/docs/reference/cli/<name>.mdx, and every non-index.mdx page in that directory must correspond to exactly one top-level command.

Files:

  • docs/src/content/docs/reference/cli/session-context.mdx
docs/src/content/docs/reference/cli/*.mdx

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

Do not create separate reference pages for subcommands; subcommand documentation must stay inline within the parent command’s .mdx page.

Every parent CLI reference page in docs/src/content/docs/reference/cli/ must contain headings for its direct subcommands using the exact archgate <parent> <sub> format, and must not keep headings for subcommands that no longer exist.

Files:

  • docs/src/content/docs/reference/cli/session-context.mdx
{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}

📄 CodeRabbit inference engine (.archgate/adrs/ARCH-015-cli-command-documentation-coverage.md)

{src/commands/{*.ts,*/index.ts},docs/src/content/docs/reference/cli/!(index).mdx}: When a top-level command is added, its docs page must be added in the same change; when a top-level command is removed, its matching docs page must be deleted in the same change.
Keep command and documentation stems aligned when renaming a top-level command file; renaming src/commands/<name>.ts or src/commands/<name>/index.ts must be paired with renaming docs/src/content/docs/reference/cli/<name>.mdx.

Files:

  • docs/src/content/docs/reference/cli/session-context.mdx
docs/src/content/docs/reference/**/*.mdx

📄 CodeRabbit inference engine (.archgate/adrs/GEN-001-documentation-site.md)

Keep reference pages accurate to the CLI source code; when CLI APIs change, update the corresponding reference docs in the same PR.

Files:

  • docs/src/content/docs/reference/cli/session-context.mdx
🧠 Learnings (1)
📚 Learning: 2026-06-11T12:50:28.661Z
Learnt from: rhuanbarreto
Repo: archgate/cli PR: 406
File: .claude/agent-memory/archgate-developer/feedback_prefer_tests_over_adr_rules.md:8-18
Timestamp: 2026-06-11T12:50:28.661Z
Learning: In `archgate/cli`, for markdown files under `.claude/agent-memory/`, follow the established convention: use YAML frontmatter (with a `name:` field used as the document title) and do not require a top-level `#` (H1) heading. During code review, do not flag missing first-line/first-top-level H1 headings (e.g., MD041) for these agent-memory files since markdownlint is not part of the repo’s `bun run validate` lint pipeline (oxlint/oxfmt only).

Applied to files:

  • .claude/agent-memory/archgate-developer/project_session_context_skip_root_fix.md
  • .claude/agent-memory/archgate-developer/project_plugins_generated_skill_files.md
  • .claude/agent-memory/archgate-developer/MEMORY.md
🪛 LanguageTool
docs/src/content/docs/pt-br/reference/cli/session-context.mdx

[style] ~65-~65: Evite abreviações de internet. Considere escrever “não” por extenso. Se quis dizer “n”, coloque entre aspas.
Context: ...----------------------------------- | | --max-entries <n> | Máximo de entradas a retornar (padr...

(INTERNET_ABBREVIATIONS)


[style] ~66-~66: Evite abreviações de internet. Considere escrever “não” por extenso. Se quis dizer “n”, coloque entre aspas.
Context: ... | | --skip <n> | Pular as N sessões mais rece...

(INTERNET_ABBREVIATIONS)


[grammar] ~66-~66: Possível erro de concordância de número.
Context: ...ne com --root para pular apenas entre sessões raiz) | ...

(GENERAL_NUMBER_AGREEMENT_ERRORS)


[uncategorized] ~70-~70: Pontuação duplicada
Context: ... de pai/filho entre sessões. Um simples --skip 1 seleciona a segunda sessão mais ...

(DOUBLE_PUNCTUATION_XML)


[uncategorized] ~70-~70: Esta locução deve ser separada por vírgulas.
Context: ...iretório, o que só corresponde à sessão pai quando exatamente uma outra sessão a to...

(VERB_COMMA_CONJUNCTION)


[style] ~70-~70: “uma outra” é um pleonasmo. É preferível dizer “outra”
Context: ...responde à sessão pai quando exatamente uma outra sessão a tocou — um leque de sub-agente...

(PT_REDUNDANCY_REPLACE_UMA_OUTRA)


[uncategorized] ~70-~70: Pontuação duplicada
Context: ...ip 1cair em um irmão não relacionado.--root` resolve diretamente a sessão de ní...

(DOUBLE_PUNCTUATION_XML)

🪛 markdownlint-cli2 (0.22.1)
.claude/agent-memory/archgate-developer/project_session_context_skip_root_fix.md

[warning] 8-8: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

.claude/agent-memory/archgate-developer/project_plugins_generated_skill_files.md

[warning] 8-8: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🔇 Additional comments (13)
.claude/agent-memory/archgate-developer/MEMORY.md (1)

83-87: LGTM!

.claude/agent-memory/archgate-developer/project_plugins_generated_skill_files.md (1)

1-13: LGTM!

.claude/agent-memory/archgate-developer/project_session_context_skip_root_fix.md (1)

1-17: LGTM!

docs/public/llms-full.txt (1)

4391-4404: LGTM!

Also applies to: 4438-4443

docs/src/content/docs/nb/reference/cli/session-context.mdx (1)

57-70: LGTM!

Also applies to: 103-108

docs/src/content/docs/pt-br/reference/cli/session-context.mdx (1)

57-70: LGTM!

Also applies to: 103-108

docs/src/content/docs/reference/cli/session-context.mdx (2)

59-70: LGTM!

Also applies to: 103-108


57-58: 🎯 Functional Correctness

directory is the right session-matching field here. The opencode session-row schema uses directory for project matching.

src/helpers/session-context-opencode.ts (2)

26-37: LGTM!


65-70: LGTM!

Also applies to: 101-105

src/commands/session-context/opencode.ts (1)

19-19: LGTM!

Also applies to: 31-42

tests/commands/session-context/opencode.test.ts (1)

73-80: LGTM!

Also applies to: 195-213

tests/helpers/session-context-opencode-root.test.ts (1)

128-156: LGTM!

Comment on lines +127 to +154
// 3. Select session by ID, or by recency within the chosen scope (with
// optional skip).
//
// Opencode records true parent/child linkage via `parent_id` — a
// sub-agent session (or an inline Skill invocation, which appends to the
// *current* session row instead of creating a new one) can have any
// number of sibling sessions sharing this directory. Sorting every
// matching session by recency and taking the Nth (the plain `--skip`
// behavior) cannot reliably reach "the top-level session": once more
// than one sibling exists, whichever sibling was touched most recently
// occupies that slot, not the parent. `root: true` sidesteps the
// guesswork by filtering to sessions with no parent (the true root of
// the tree) before applying `skip`.
const skip = options?.skip ?? 0;
const rootOnly = options?.root ?? false;
const candidates = rootOnly
? matching.filter((s) => s.parent_id === null)
: matching;
const target = options?.sessionId
? matching.find((s) => s.id === options.sessionId)
: matching[skip];
: candidates[skip];

if (!target) {
const scope = rootOnly ? "root session(s)" : "session(s)";
const error = options?.sessionId
? `Session not found: ${options.sessionId}`
: `Only ${String(matching.length)} session(s) available but --skip ${String(skip)} requested`;
return { ok: false, error, available: matching.map((s) => s.id) };
: `Only ${String(candidates.length)} ${scope} available but --skip ${String(skip)} requested`;
return { ok: false, error, available: candidates.map((s) => s.id) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

available list is inconsistent with the actual search scope when sessionId + root are combined.

When options.sessionId is set, lookup uses the unscoped matching list (Line 146), but on failure the available field always reports candidates (Line 154), which is root-filtered when root: true. If a user passes --session-id X --root and X exists in matching but isn't a root session, the error will misleadingly omit X from available even though it was actually searched against the full matching set.

🐛 Proposed fix
     if (!target) {
       const scope = rootOnly ? "root session(s)" : "session(s)";
       const error = options?.sessionId
         ? `Session not found: ${options.sessionId}`
         : `Only ${String(candidates.length)} ${scope} available but --skip ${String(skip)} requested`;
-      return { ok: false, error, available: candidates.map((s) => s.id) };
+      const searched = options?.sessionId ? matching : candidates;
+      return { ok: false, error, available: searched.map((s) => s.id) };
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 3. Select session by ID, or by recency within the chosen scope (with
// optional skip).
//
// Opencode records true parent/child linkage via `parent_id` — a
// sub-agent session (or an inline Skill invocation, which appends to the
// *current* session row instead of creating a new one) can have any
// number of sibling sessions sharing this directory. Sorting every
// matching session by recency and taking the Nth (the plain `--skip`
// behavior) cannot reliably reach "the top-level session": once more
// than one sibling exists, whichever sibling was touched most recently
// occupies that slot, not the parent. `root: true` sidesteps the
// guesswork by filtering to sessions with no parent (the true root of
// the tree) before applying `skip`.
const skip = options?.skip ?? 0;
const rootOnly = options?.root ?? false;
const candidates = rootOnly
? matching.filter((s) => s.parent_id === null)
: matching;
const target = options?.sessionId
? matching.find((s) => s.id === options.sessionId)
: matching[skip];
: candidates[skip];
if (!target) {
const scope = rootOnly ? "root session(s)" : "session(s)";
const error = options?.sessionId
? `Session not found: ${options.sessionId}`
: `Only ${String(matching.length)} session(s) available but --skip ${String(skip)} requested`;
return { ok: false, error, available: matching.map((s) => s.id) };
: `Only ${String(candidates.length)} ${scope} available but --skip ${String(skip)} requested`;
return { ok: false, error, available: candidates.map((s) => s.id) };
if (!target) {
const scope = rootOnly ? "root session(s)" : "session(s)";
const error = options?.sessionId
? `Session not found: ${options.sessionId}`
: `Only ${String(candidates.length)} ${scope} available but --skip ${String(skip)} requested`;
const searched = options?.sessionId ? matching : candidates;
return { ok: false, error, available: searched.map((s) => s.id) };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/helpers/session-context-opencode.ts` around lines 127 - 154, The
`available` result in `src/helpers/session-context-opencode.ts` is using the
wrong scope when `options.sessionId` is set alongside `root`. Update the failure
path in the session selection logic so the `available` list matches the actual
lookup set used by the `target` resolution in that branch (`matching` for
`sessionId`, `candidates` for skip-based selection), and keep the scope
label/error text aligned with that choice in the `rootOnly`/`skip` handling.

Comment on lines +109 to +126
test("root reads the true parent even when sibling sub-agents are more recent", async () => {
// Real-world fan-out: one parent spawns sibling sub-agent sessions
// against the same directory. Plain --skip 1 lands on whichever sibling
// sits second in recency order, not the parent — --root fixes this by
// filtering to parent_id IS NULL first.
const db = createDb();
makeSession(db, "ses_parent", 1000);
makeSession(db, "ses_sibling_a", 3000, "ses_parent");
makeSession(db, "ses_sibling_b", 2000, "ses_parent");
db.close();

const skipped = await readOpencodeSession(projectRoot, { skip: 1 });
if (skipped.ok) expect(skipped.data.sessionId).toBe("ses_sibling_b");

const rooted = await readOpencodeSession(projectRoot, { root: true });
expect(rooted.ok).toBe(true);
if (rooted.ok) expect(rooted.data.sessionId).toBe("ses_parent");
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the "before" control assertion to avoid silently no-op'ing.

Line 121's expect is guarded by if (skipped.ok); if skipped.ok were unexpectedly false, this comparison — which documents the exact problem --root fixes — silently never runs, while the test still passes via the unconditional assertion on rooted.ok (Line 124).

♻️ Proposed fix
     const skipped = await readOpencodeSession(projectRoot, { skip: 1 });
-    if (skipped.ok) expect(skipped.data.sessionId).toBe("ses_sibling_b");
+    expect(skipped.ok).toBe(true);
+    if (skipped.ok) expect(skipped.data.sessionId).toBe("ses_sibling_b");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("root reads the true parent even when sibling sub-agents are more recent", async () => {
// Real-world fan-out: one parent spawns sibling sub-agent sessions
// against the same directory. Plain --skip 1 lands on whichever sibling
// sits second in recency order, not the parent — --root fixes this by
// filtering to parent_id IS NULL first.
const db = createDb();
makeSession(db, "ses_parent", 1000);
makeSession(db, "ses_sibling_a", 3000, "ses_parent");
makeSession(db, "ses_sibling_b", 2000, "ses_parent");
db.close();
const skipped = await readOpencodeSession(projectRoot, { skip: 1 });
if (skipped.ok) expect(skipped.data.sessionId).toBe("ses_sibling_b");
const rooted = await readOpencodeSession(projectRoot, { root: true });
expect(rooted.ok).toBe(true);
if (rooted.ok) expect(rooted.data.sessionId).toBe("ses_parent");
});
test("root reads the true parent even when sibling sub-agents are more recent", async () => {
// Real-world fan-out: one parent spawns sibling sub-agent sessions
// against the same directory. Plain --skip 1 lands on whichever sibling
// sits second in recency order, not the parent — --root fixes this by
// filtering to parent_id IS NULL first.
const db = createDb();
makeSession(db, "ses_parent", 1000);
makeSession(db, "ses_sibling_a", 3000, "ses_parent");
makeSession(db, "ses_sibling_b", 2000, "ses_parent");
db.close();
const skipped = await readOpencodeSession(projectRoot, { skip: 1 });
expect(skipped.ok).toBe(true);
if (skipped.ok) expect(skipped.data.sessionId).toBe("ses_sibling_b");
const rooted = await readOpencodeSession(projectRoot, { root: true });
expect(rooted.ok).toBe(true);
if (rooted.ok) expect(rooted.data.sessionId).toBe("ses_parent");
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/helpers/session-context-opencode-root.test.ts` around lines 109 - 126,
The control assertion in readOpencodeSession test is being skipped if skipped.ok
is false, so the “before” case can silently no-op and still let the test pass.
Update the test case in test("root reads the true parent even when sibling
sub-agents are more recent") to assert skipped.ok before checking
skipped.data.sessionId, using the existing readOpencodeSession result variable
so the precondition is always enforced.

rhuanbarreto added a commit that referenced this pull request Jul 1, 2026
- Port the sibling fan-out regression test from #444, adapted to the
  new semantics (--skip 1 errors honestly; default and --root resolve
  the parent) with unconditional control assertions
- Enrich the root option JSDoc with the inline-skill / sibling fan-out
  rationale
- Document the multi-top-level-session caveat and deterministic
  resolution via --session-id <id> --root in the CLI reference
  (en/nb/pt-br) and regenerate llms-full.txt
- pt-br: add the missing "sempre" nuance to the --skip row
- Port agent-memory captures from #444 (generated plugins skill files;
  session-context skip/root fix incl. the open claude-code/cursor
  follow-up)

Claude-Session: https://claude.ai/code/session_01SGjSjrywTnZDcUqgHWGrTj
Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@rhuanbarreto

rhuanbarreto commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #445, which solves the same bug at the root: recency selection now excludes sub-agent child sessions by default (instead of keeping the broken default behind an opt-in flag), and --root gets deterministic semantics — with --session-id <child> it walks the parent_id chain to the top-level ancestor, which also addresses Bugbot's multi-conversation finding on this PR.

The valuable parts of this PR were incorporated into #445 (commit 918c087):

  • The sibling fan-out regression test (adapted to the new semantics, with the control assertions made unconditional per CodeRabbit's feedback here)
  • The richer root option JSDoc rationale (inline-skill / sibling fan-out)
  • The multi-top-level-session caveat, now documented in the CLI reference (en/nb/pt-br)
  • The open follow-up that the same class of bug likely affects claude-code/cursor/copilot --skip 1 guidance

The companion plugin-side change from this effort stays and has been updated to match #445's final CLI semantics.

@rhuanbarreto rhuanbarreto deleted the fix/opencode-session-context-root branch July 2, 2026 08:08
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.

1 participant