Skip to content

feat(cli): add session path status command#4124

Merged
wenshao merged 7 commits into
QwenLM:mainfrom
kkhomej33-netizen:feat/status-paths
May 20, 2026
Merged

feat(cli): add session path status command#4124
wenshao merged 7 commits into
QwenLM:mainfrom
kkhomej33-netizen:feat/status-paths

Conversation

@kkhomej33-netizen

@kkhomej33-netizen kkhomej33-netizen commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • What changed:
    • Added /status paths to print the current session transcript, debug log, plan file, and OpenAI log paths when those artifacts are enabled or present.
    • Added stable OpenAI log context metadata so logs can be correlated with the current session.
    • Documented the new command.
  • Why it changed:
    • This is an active-session diagnostic/discoverability command for Qwen Code users who want to self-diagnose model/tool behavior or quickly locate session artifacts.
    • A typical use case is checking the current session's OpenAI logging when a model response does not match the user's intent, so the user can tell whether the issue came from the model/API response or from Qwen Code context construction, tool calls, tool results, or orchestration.
    • Another use case is locating the active transcript/session file for precise inspection, recovery, or manual session-history edits.
    • The output is intentionally plain text so terminal integrations such as VS Code can detect long file paths as clickable links.
  • Reviewer focus:
    • Whether /status is the right home for read-only active-session diagnostics.
    • Whether the command shows only relevant artifacts that are enabled or currently present.
    • Whether the plain text format is useful for terminal path detection.
    • Whether OpenAI log session correlation is lightweight enough for existing logs.

Validation

  • Commands run:
    cd packages/cli && npx vitest run src/utils/sessionPaths.test.ts src/ui/commands/aboutCommand.test.ts src/gemini.test.tsx
    cd packages/core && npx vitest run src/utils/openaiLogger.test.ts src/agents/runtime/agent-core.test.ts
    npm run build
    npm run typecheck
    npm run lint
    git diff --check
  • Prompts / inputs used:
    • N/A
  • Expected result:
    • /status paths returns plain text paths for the current session artifacts.
    • Disabled or missing artifacts are omitted.
    • OpenAI logs can be matched back to the current session when OpenAI logging is enabled.
  • Observed result:
    • Focused CLI and core tests passed.
    • Build passed.
    • Typecheck passed.
    • Lint and whitespace checks passed.
  • Quickest reviewer verification path:
    • Run qwen-code, then run /status paths.
    • Enable debug mode or OpenAI logging and rerun /status paths to see those paths appear.
  • Evidence (output, logs, screenshots, video, JSON, before/after, etc.):
    • Unit tests cover shown/hidden session artifacts, unknown-session fallback, missing/unreadable OpenAI log directories, OpenAI log matching, and non-interactive prompt/session correlation.

Scope / Risk

  • Main risk or tradeoff:
    • The command intentionally uses plain text rather than a richer TUI box to keep long paths clickable/copyable in terminals.
  • Not covered / not validated:
    • No screenshot or video captured.
  • Breaking changes / migration notes:
    • None.

Testing Matrix

🍏 🪟 🐧
npm run ⚠️ ⚠️
npx ⚠️ ⚠️
Docker N/A N/A N/A
Podman N/A N/A N/A
Seatbelt N/A N/A N/A

Testing matrix notes:

  • Validated locally on macOS. CI covers macOS, Ubuntu, and Windows.

Linked Issues / Bugs

  • N/A

Comment thread packages/core/src/utils/openaiLogger.ts Fixed
): Promise<string | undefined> {
const files = await listLogFiles(logDir, (name) =>
/^openai-.*\.json$/.test(name),
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] findLatestOpenAILogForSession iterates and fully JSON-parses every openai-*.json file in the log directory with no upper bound on the number of files scanned. Each file is loaded entirely into memory and parsed, only to check a single context.sessionId field.

Over weeks of usage, the OpenAI log directory can accumulate hundreds of large files (containing base64-encoded images, tool results, etc.), causing this interactive slash command to degrade to tens of seconds of blocking I/O. OpenAILogger.getLogFiles() in packages/core/src/utils/openaiLogger.ts already supports a limit parameter — this new code reimplements the same directory listing without it.

Suggested change
);
const MAX_SCAN = 100;
const files = await listLogFiles(logDir, (name) =>
/^openai-.*\.json$/.test(name),
);
const recentFiles = files.slice(0, MAX_SCAN);
for (const file of recentFiles) {

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in fe50141 by limiting the lookup to the 100 most recent OpenAI log files. I also added a regression test that asserts only 100 JSON files are read during the lookup.

return [];
}
throw error;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] listLogFiles catches only ENOENT and re-throws all other filesystem errors (e.g., EACCES, EPERM). Since collectSessionPathInfo does not wrap the findLatestOpenAILogForSession call in a try/catch, a permission error on the OpenAI log directory will cause the entire /status paths command to fail — the user gets no output about any session paths, not just the log section.

OpenAILogger.getLogFiles() (same module, line ~190) handles all errors gracefully by returning [], which would be a more consistent approach for a diagnostic command.

Suggested change
}
} catch {
return [];
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in fe50141. listLogFiles now degrades to an empty result for directory read failures, while logging non-ENOENT failures through the debug logger so /status paths can still render the rest of the session paths.

const parsed: unknown = JSON.parse(raw);
if (hasContextSessionId(parsed, sessionId)) {
return file;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The bare catch {} block swallows all error types — not just SyntaxError from malformed JSON, but also EACCES (permission denied), ENOMEM (out of memory reading large files), and EIO (disk I/O errors). The comment says "Ignore malformed or concurrently-written log files" but the implementation is far broader.

This silently drops potentially important signals while yielding no diagnostic output. If a log file is unreadable due to a configuration/permission issue, the user gets no indication.

Suggested change
}
} catch (err) {
if (
err instanceof SyntaxError ||
(err as NodeJS.ErrnoException).code === 'ENOENT'
) {
continue;
}
debugLogger.warn('Error reading log file %s: %o', file, err);
}

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in fe50141. The per-file catch now only silently skips malformed JSON and concurrently missing files. Other read errors are logged via the SESSION_PATHS debug logger before continuing the scan.

const parts = promptId.split('#');
if (parts.length === 3 && parts[0]) {
return parts[0];
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] sessionIdFromPromptId uses parts.length === 3 as an exact match for subagent-format promptIds ({sessionId}#{subagentId}#{turnCounter}). If a subagent name ever contains a # character, the resulting 4+ segments will not match, and sessionId silently drops to undefined.

Since the first #-delimited segment is always the session UUID (which never contains #), the check can be made defensive:

Suggested change
}
if (parts.length >= 3 && parts[0]) {

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in fe50141. sessionIdFromPromptId now accepts 3+ hash-delimited segments and keeps the first segment as the session id, with coverage for extra separators in subagent prompt ids.

function contextForPromptId(
promptId: string | undefined,
): OpenAILogContext | null {
const trimmedPromptId = promptId?.trim();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] contextForPromptId can produce log entries with only { promptId } and no sessionId when the promptId is a bare UUID (no ######## delimiter, no subagent #...#...# pattern). This occurs during session initialization before the first turn adds the ########counter suffix.

Logs written during this initialization window will be invisible to findLatestOpenAILogForSession, which searches by context.sessionId. /status paths will report "none yet" even though matching logs exist on disk.

Consider adding a UUID fallback in sessionIdFromPromptId: if promptId matches a UUID regex, return the whole promptId as the sessionId.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in fe50141. Bare UUID prompt ids now map back to the same value as sessionId, with OpenAILogger coverage for session-initialization style prompt ids.

Comment thread packages/cli/src/utils/sessionPaths.ts Outdated
: undefined;
const transcriptPath = config?.getTranscriptPath() || '';
const debugLogPath =
config?.getDebugMode() === true && sessionId !== 'unknown'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] config?.getDebugMode() === true uses strict boolean comparison, but the rest of the codebase consistently uses truthy checks (e.g., if (config.getDebugMode()) in Footer.tsx, KeypressContext.tsx, DebugModeNotification.tsx, gemini.tsx).

If getDebugMode() ever returns a truthy non-boolean value (e.g., if the config layer evolves), the debug log path will be silently omitted from /status paths output, while debug logs are still being written — causing silent diagnostic misdirection.

Suggested change
config?.getDebugMode() === true && sessionId !== 'unknown'
config?.getDebugMode() && sessionId !== 'unknown'

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Accepted in fe50141 for consistency with the rest of the codebase. getDebugMode currently returns boolean, but /status paths now uses the same truthy check style as the existing debug-mode call sites.

Comment thread packages/cli/src/utils/sessionPaths.ts Outdated
if (!context || typeof context !== 'object') {
return false;
}
return (context as { sessionId?: unknown }).sessionId === sessionId;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] hasContextSessionId only checks context.sessionId, but contextForPromptId (introduced in the same PR) can produce log entries with only context.promptId and no sessionId field. When that happens, findLatestOpenAILogForSession will skip past a matching log file, and /status paths will report "none yet" even though the file exists.

Consider adding a context.promptId fallback match as defense-in-depth:

Suggested change
return (context as { sessionId?: unknown }).sessionId === sessionId;
const ctx = context as { sessionId?: unknown; promptId?: unknown };
return ctx.sessionId === sessionId || ctx.promptId === sessionId;

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in fe50141. hasContextSessionId now also matches context.promptId against the current session id, and sessionPaths has regression coverage for logs that only contain promptId.

const openAILoggingEnabled =
contentGeneratorConfig?.enableOpenAILogging === true;
const latestOpenAILog =
openAILoggingEnabled && sessionId !== 'unknown'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The three sessionId === 'unknown' short-circuit branches (L48, L53, L56) that skip OpenAI log scanning, debug log path, and plan file path respectively — are untested. sessionPaths.test.ts always uses real session IDs. If getSessionId() returns empty and stats.sessionId is undefined (e.g. session not yet fully initialized when /status paths is called), the behavior is unverified.

Adding a test with getSessionId: vi.fn().mockReturnValue('') and session.stats.sessionId: undefined would cover this edge case.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@tanzhenxin tanzhenxin added the type/feature-request New feature or enhancement request label May 15, 2026

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review

Stepping back from the code: I'd like to align on the product surface before this lands. The PR body frames this as a support workflow — "maintainers can ask users to run one command and get the files needed to inspect a session." But the design seems to point at a different user, a developer working on qwen-code itself with all flags enabled by default.

Three places where the design contradicts the stated target:

  • The command lives inside a running session, so an end user whose qwen-code just crashed — exactly when a maintainer would ask for artifacts — can't invoke it at all.
  • Rows are hidden when the underlying feature flag is off. An end user reporting a bug doesn't know QWEN_DEBUG_LOG_FILE or enableOpenAILogging exist; the command quietly hides the very thing the maintainer would need them to turn on.
  • It lives under /status (qwen-code's developer info dump), not under /export where users naturally look when asked for artifacts to share.

If the real target is the end-user-reporting-a-bug flow, the primitive we want is closer to /export support-bundle — one command, one zip in CWD with transcript + debug log + recent OpenAI logs + system info, ready to attach to a GitHub issue. That keeps /export as the single place users go for session artifacts, and the user never has to know about feature flags or paste filesystem paths.

If the real target is "developers debugging qwen-code," that's a fine goal in itself, but then the PR should say so and we should talk about whether the slash command surface is the right home for developer-only diagnostics versus a clearly-namespaced developer surface.

Could you say more about who you had in mind and how they'd actually use this? I'd rather hold off merging until we've aligned on the target user — I have separate notes on code-correctness issues in the follow-up commits, but those only matter once we agree this is the right shape.

Verdict

REQUEST_CHANGES — blocking on the target-user question.

@kkhomej33-netizen

Copy link
Copy Markdown
Contributor Author

Thanks, this is a very fair point. I agree the PR body framed the motivation poorly.

The target I had in mind is not really the "end user whose session crashed and needs to attach a complete support bundle to an issue" flow. For that case, I agree /export support-bundle would be a better primitive: one command, one attachable artifact, no need for the user to understand feature flags or internal paths.

The use case here is closer to self-diagnosis and productivity for a Qwen Code user while the session is still running.

Two concrete examples:

  1. I'm using Qwen Code on a real project, and the model response does not match my intent. At that point I may want to inspect the OpenAI logging for the current session to understand whether the issue came from the model/API response itself, or from Qwen Code's side: context construction, tool calls, tool results, or orchestration. /status paths gives me the exact log/session files for the current conversation without having to search through runtime directories.

  2. A user wants to inspect or carefully edit the session history. Being able to quickly locate the transcript/session file for the active conversation is useful for debugging, recovery, or manual correction workflows.

So I would reframe this as a read-only discoverability/diagnostic command for active-session self-service, not as the full support-reporting workflow. With that framing, hiding rows for artifacts that are not enabled/present is intentional: the command shows the concrete files that currently exist for this session, rather than guiding the user through enabling every possible diagnostic artifact.

I can update the PR description/docs to make this target clearer and avoid presenting it as a maintainer support-bundle workflow.

): Promise<SessionPathInfo> {
const config = context.services.config;
const sessionId =
config?.getSessionId() || context.session.stats.sessionId || 'unknown';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Critical] sessionId = 'unknown' 降级路径及 L48/L53/L58 三处短路分支均无测试覆盖。当 getSessionId() 返回 falsy 且 context.session.stats.sessionId 也为空时,sessionId 回退为 'unknown',触发 OpenAI 日志扫描、debug 日志路径、plan 文件路径全部跳过。所有现有测试均 mock 了真实 UUID,此完整优雅降级路径完全未被验证,回归 bug 无法被检测到。

Suggested change
config?.getSessionId() || context.session.stats.sessionId || 'unknown';
// 添加测试:构造 getSessionId() 返回 undefined、stats.sessionId 为空的 CommandContext,
// 断言输出包含 "Session ID: unknown",省略 "Debug log:" 和 "Plan file:",
// spy fs.readdir 确认日志目录从未被扫描。

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ff18330. Added regression coverage for the unknown fallback with empty getSessionId() and no stats.sessionId; it asserts Session ID: unknown, omits derived debug/plan paths, and verifies OpenAI log scanning (fs.readdir) plus plan existence probing (fs.access) are skipped.

Comment thread packages/cli/src/utils/sessionPaths.ts Outdated
return entries
.filter((entry) => entry.isFile() && predicate(entry.name))
.map((entry) => path.join(dir, entry.name))
.sort()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] listLogFiles 对所有匹配文件完整排序(O(n log n)),但仅使用前 100 个。大型日志目录(数千文件)会浪费 CPU 和内存。

Suggested change
.sort()
.sort()
.slice(-OPENAI_LOG_SCAN_LIMIT);

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ff18330. listLogFiles now keeps a bounded descending list of the most recent 100 matching paths while walking directory entries, so it no longer materializes and sorts every matching log file before discarding all but 100.

.sort()
.reverse();
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] listLogFiles 的 ENOENT catch 分支(静默返回 [])无测试覆盖。仅测试了 EACCES 权限拒绝路径;日志目录不存在是启用日志前的正常情况,此路径的回归会破坏整个 /status paths 命令。

Suggested change
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
// 添加测试:mock fs.readdir 拒绝 { code: 'ENOENT' },
// 断言 collectSessionPathInfo 仍正常返回且无 debug 警告。

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ff18330. Added ENOENT regression coverage for the normal missing-log-directory path; /status paths still renders the session output and reports Latest for session: none yet, while the implementation keeps ENOENT silent.

Comment thread packages/cli/src/gemini.tsx Outdated
);

const prompt_id = Math.random().toString(16).slice(2);
const prompt_id = randomUUID();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] prompt_id = randomUUID() 产生裸 UUID,导致日志 context.sessionId 被设为 prompt UUID 而非真实 sessionId。/status pathsconfig.getSessionId() 搜索日志时无法匹配此路径的日志条目。其他所有执行路径(交互式、非交互式会话、ACP)使用 {sessionId}########{counter} 格式,可被正确匹配。

Suggested change
const prompt_id = randomUUID();
const prompt_id = config.getSessionId() + '########0';

— DeepSeek/deepseek-v4-pro via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ff18330. The non-interactive prompt id now uses the current session id format ({sessionId}########0) via createNonInteractivePromptId(config.getSessionId()), with a focused unit test for the format. This keeps OpenAI log context.sessionId aligned with the /status paths lookup.

@kkhomej33-netizen

Copy link
Copy Markdown
Contributor Author

Follow-up update:

  • Synced this branch with latest upstream/main and resolved the /status test conflict with the new LSP status coverage.
  • Updated the PR description to reframe /status paths as an active-session self-diagnosis/productivity command rather than a full support-bundle flow.
  • Addressed the latest review feedback in ff18330:
    • added sessionId === "unknown" fallback coverage;
    • added missing OpenAI log directory (ENOENT) coverage;
    • changed OpenAI log lookup to keep only a bounded recent-file window;
    • changed the non-interactive prompt id back to {sessionId}########0 so OpenAI logs correlate with /status paths.

Validation run locally:

cd packages/cli && npx vitest run src/utils/sessionPaths.test.ts src/ui/commands/aboutCommand.test.ts src/gemini.test.tsx
cd packages/core && npx vitest run src/utils/openaiLogger.test.ts src/agents/runtime/agent-core.test.ts
npm run build
npm run typecheck
npm run lint
git diff --check

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Additional observations:

  1. Subagent ID entropy regression (agent-core.ts:275): randomUUID().replace(/-/g, '').slice(0, 6) yields 6 hex chars (24 bits, ~16.7M combinations). The old Math.random().toString(36).slice(2, 8) yielded 6 base-36 chars (~31 bits, ~2.2B combinations). Crypto quality improved but collision resistance dropped ~130x. Consider slice(0, 8) (32 bits) to exceed the old entropy.

  2. formatSessionPathInfo not directly tested (sessionPaths.test.ts): The formatting logic (section titles, 2-space indentation, blank-line separators between sections) is only exercised indirectly through collectSessionPathInfo. A dedicated unit test with known input would catch indentation/separator regressions.

— mimo-v2.5-pro via Qwen Code /review

}
}

export function createNonInteractivePromptId(sessionId: string): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] createNonInteractivePromptId is exported but always returns the same deterministic string ({sessionId}########0) for every call within a session. The function name implies uniqueness, and downstream log-analysis tools that group by prompt_id will merge distinct non-interactive requests into one.

Consider either renaming to createSessionCorrelationId to set correct expectations, or appending a monotonic counter:

Suggested change
export function createNonInteractivePromptId(sessionId: string): string {
let nonInteractivePromptCounter = 0;
export function createNonInteractivePromptId(sessionId: string): string {
return `${sessionId}########${nonInteractivePromptCounter++}`;
}

— mimo-v2.5-pro via Qwen Code /review

}

export function createNonInteractivePromptId(sessionId: string): string {
return `${sessionId}########0`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The ######## delimiter is hardcoded here but defined as MAIN_SESSION_PROMPT_ID_DELIMITER (unexported) in openaiLogger.ts. If the delimiter changes in core, this CLI code will silently produce IDs that sessionIdFromPromptId cannot parse, breaking OpenAI log session correlation.

Consider exporting the constant from core and importing it here:

import { MAIN_SESSION_PROMPT_ID_DELIMITER } from '@qwen-code/qwen-code-core';

— mimo-v2.5-pro via Qwen Code /review

}

export function resolveOpenAILogDir(
customLogDir?: string,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] resolveOpenAILogDir is a newly exported public API (re-exported from core/index.ts) with non-obvious path-resolution semantics. Consider adding JSDoc:

Suggested change
customLogDir?: string,
/**
* Resolve the OpenAI log directory path.
*
* - If `customLogDir` is omitted, defaults to `<cwd>/logs/openai`.
* - Supports `~` expansion via `os.homedir()`.
* - Relative paths are resolved against `cwd` (or `process.cwd()`).
*
* @param customLogDir Optional custom directory (absolute, relative, or `~/...`).
* @param cwd Working directory for relative-path resolution.
*/
export function resolveOpenAILogDir(

— mimo-v2.5-pro via Qwen Code /review

@kkhomej33-netizen

Copy link
Copy Markdown
Contributor Author

Addressed both observations in a472595:

  • Increased the secure subagent id suffix from 6 hex chars to 8 hex chars, restoring the suffix space to 32 bits and exceeding the old 6-char base36 entropy.
  • Added a direct formatSessionPathInfo unit test that locks section titles, 2-space indentation, and blank-line separators.

Validation:

cd packages/cli && npx vitest run src/utils/sessionPaths.test.ts
cd packages/core && npx vitest run src/agents/runtime/agent-core.test.ts
cd packages/cli && npx prettier --check src/utils/sessionPaths.test.ts
cd packages/core && npx prettier --check src/agents/runtime/agent-core.ts
git diff --check

@tanzhenxin tanzhenxin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review

Re-review after the pivot, and this clears my earlier block. That review was a design question — whether /status paths was a maintainer support-bundle workflow (wrong shape) or something else. You answered it: this is an active-session self-diagnosis and artifact-discoverability command for the user themselves, with /export support-bundle left as the separate primitive for support reporting. Under that framing the previously-suspect behaviors are correct by design — only surfacing artifacts that are present or that the user opted into is exactly right for a "what's relevant to this session" command, and /status is a reasonable home for read-only diagnostics.

The load-bearing change — making the non-interactive prompt id session-correlated so the OpenAI logs can be tied back to the current session — is correct end to end and consistent with the interactive and stream-json id formats. An independent review pass found no functional regressions. The debug-log row appearing only under --debug is intentional surfacing behavior, not a correctness issue. The remaining nits (a dead fallback branch, English-only output labels, one thin test) are low-severity polish and don't block.

Verdict

APPROVE — Design concern from the prior review is resolved and the session-correlation fix is solid. Minor polish items noted are non-blocking.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-review Summary

All previously reported issues have been addressed in the latest commits. The one remaining suggestion — the ######## session delimiter being defined as an unexported constant in core while hardcoded in CLI — was already noted in a prior inline comment.

No new high-confidence findings. The implementation is solid: tests pass (80/80), CI is green, and error handling covers the main failure paths.

Verdict: LGTM from a code perspective. The open delimiter coupling suggestion is non-blocking.

— DeepSeek/deepseek-v4-pro via Qwen Code /review

@kkhomej33-netizen

Copy link
Copy Markdown
Contributor Author

Hi @wenshao, the requested changes and follow-up observations should now be addressed, and CI is green.

Could you please re-review and approve when you have a chance? The PR is still blocked by the earlier changes-requested review. Thanks!

@wenshao

wenshao commented May 20, 2026

Copy link
Copy Markdown
Collaborator

✅ Local Verification Report — PASS

Verified this PR by building and driving the actual CLI at its real surfaces (interactive TUI + non-interactive), not by re-running test suites.

Environment

  • Built from PR head a4725951enpm run build
  • CLI node packages/cli v0.15.11 · Node v22.22.2 · Linux x64
  • Interactive runs driven inside an isolated tmux session in a throwaway workspace
  • OpenAI logging enabled via a workspace-local .qwen/settings.json (model.enableOpenAILogging: true) — user config left untouched

What was verified

# Check Result
1 /status autocomplete lists the new paths subcommand + i18n description
2 /status paths prints Session files: (Session ID + Transcript); transcript file confirmed on disk
3 Bare /status still renders the full Status box — no regression from the added subCommands
4 --debug/status paths adds Debug log: (matches the startup "Logging to:" path)
5 enableOpenAILoggingOpenAI logs: section appears (Directory + Latest for session: none yet)
6 After a real request, Latest for session resolves from none yet to the actual openai-*.json
7 Plan file present → Plan file: entry appears
8 Non-interactive qwen -p "/status paths" → plain-text output, exit 0
9 Non-interactive OpenAI log: context.promptId === ${sessionId}########0 (createNonInteractivePromptId)
10 /status <unknown> (probe) → falls back to the bare /status box 🔍

Sample — interactive /status paths

● Session files:
    Session ID: 93582771-9a25-452b-8d49-1cc3fffc0c42
    Transcript: /root/.qwen/projects/-tmp-…/chats/93582771-….jsonl
    Debug log:  /root/.qwen/debug/93582771-….txt
    Plan file:  /root/.qwen/plans/93582771-….md
  OpenAI logs:
    Directory: …/logs/openai
    Latest for session: …/logs/openai/openai-2026-05-20T08-14-20.451Z-cdb67dd8.json

OpenAI log context metadata (non-interactive run):

"context": { "promptId": "b1723903-…########0", "sessionId": "b1723903-…" }

Notes

  • End-to-end session correlation confirmed: with two different sessions' logs in one directory, a fresh session's /status paths correctly reported none yet instead of matching a foreign log — the match is genuinely session-scoped, not "newest log wins".
  • The OpenAI logger records failed requests too, so context metadata is written even when the upstream model call errors out — useful for the diagnostic use case this PR targets.
  • Minor UX: an unrecognized subcommand (/status <typo>) silently falls back to the version box with no "unknown subcommand" hint. This is existing slash-command framework behavior, not introduced by this PR, but worth noting since paths is now a typo-able subcommand.
  • The agent-core.ts subagent-id change (Math.random()randomUUID()) was not exercised at runtime (spawning a subagent needs a live model); it is covered by the PR's unit tests.

Build, interactive and non-interactive /status paths, debug mode, OpenAI log correlation, and the /status regression all check out. All observed runtime behavior matches the PR description.

@wenshao wenshao merged commit dc6a5ad into QwenLM:main May 20, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/feature-request New feature or enhancement request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants