Conversation
Co-authored-by: Copilot <[email protected]>
…ft/vscode-azurefunctions into nat/fileBasedPickProcess
Co-authored-by: Copilot <[email protected]>
…ft/vscode-azurefunctions into nat/fileBasedPickProcess
…ions into nat/fileBasedPickProcess
…ft/vscode-azurefunctions into nat/fileBasedPickProcess
…ft/vscode-azurefunctions into nat/fileBasedPickProcess
…ions into nat/fileBasedPickProcess
There was a problem hiding this comment.
Pull request overview
This PR adds a Function Host Debug view to the Azure Functions extension, providing developers with visibility into running Function Host tasks and their error output directly in the Run and Debug panel.
Changes:
- Adds a new tree view in the Debug panel showing running Function Host tasks with their errors
- Implements ANSI stripping utilities and error log detection based on terminal color codes
- Integrates GitHub Copilot Chat for AI-assisted error diagnosis
- Adds commands for viewing/copying logs and clearing errors
- Extends task provider to support additional CLI arguments in task definitions
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| src/debug/FunctionHostDebugView.ts | Tree data provider for the debug view, handles node creation and hierarchy |
| src/debug/registerFunctionHostDebugView.ts | Registers view commands (refresh, clear errors, show logs, ask Copilot) and handlers |
| src/funcCoreTools/funcHostTask.ts | Tracks running tasks, captures terminal output streams, detects and stores error logs |
| src/funcCoreTools/funcHostErrorUtils.ts | Utilities for identifying error logs via ANSI codes and extracting error context |
| src/utils/ansiUtils.ts | ANSI escape sequence removal for clean text presentation |
| src/utils/copilotChat.ts | Best-effort integration to open Copilot Chat with pre-filled prompts |
| src/debug/FuncTaskProvider.ts | Extends task definitions to support additional command-line arguments |
| src/tree/localProject/LocalProjectTreeItem.ts | Updates event handlers to use new event object pattern |
| src/commands/pickFuncProcess.ts | Returns terminal stream in API response for external consumers |
| src/extension.ts | Registers the new debug view and disposes terminal event reader on deactivation |
| package.json | Defines view contribution, commands, menus, and new setting |
| package.nls.json | Adds localized strings for the new feature |
| extension.bundle.ts | Exports new utility functions for external use |
| test/funcHostErrorContext.test.ts | Tests for error detection and context extraction |
| test/ansiUtils.test.ts | Tests for ANSI control character stripping |
| terminalEventReader = vscode.window.onDidStartTerminalShellExecution(async (terminalShellExecEvent) => { | ||
| /** | ||
| * This will pick up any terminal that, including those started outside of tasks (e.g. via the command palette). | ||
| * But we don't actually access the terminal stream until the `func host start` task starts, at which time this will be pointing to the correct terminal | ||
| * */ | ||
| latestTerminalShellExecutionEvent = terminalShellExecEvent; |
There was a problem hiding this comment.
The terminal shell execution event listener is registered before func host task starts and stores the latest event in a global variable. This creates a race condition: if multiple terminals are opened or if a non-func terminal is opened just before the func task starts, latestTerminalShellExecutionEvent could point to the wrong terminal. Consider matching terminals more precisely by correlating the task execution with the terminal, or by checking if the terminal's command matches the func host start pattern when consuming the stream.
There was a problem hiding this comment.
The main issue here is that onDidStartTerminalShellExecution always fires before onDidStartTask so if I register within onDidStartTask, it'll be too late.
The other issue is that for whatever reason, the TerminalShellExecution doesn't properly display custom tasks so I can't even determine if it's a function task by commandLine. If I look at the terminal, since it fires before the terminal UI actually updates, the terminal name is incorrect.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
There was a problem hiding this comment.
Curious to see what it comes up with.
| export function registerFunctionHostDebugView(context: vscode.ExtensionContext): void { | ||
| const provider = new FuncHostDebugViewProvider(); | ||
|
|
||
| context.subscriptions.push( | ||
| vscode.window.registerTreeDataProvider(viewId, provider), | ||
| onRunningFuncTasksChanged(() => { | ||
| provider.refresh(); | ||
| void tryOpenDebugViewOnFirstFuncHostError(); | ||
| }), | ||
| ); | ||
|
|
||
| // Ensure the context key is correct on activation. | ||
| void refreshFuncHostDebugContext(); | ||
|
|
||
| registerCommand('azureFunctions.funcHostDebug.clearErrors', async (actionContext: IActionContext) => { | ||
| actionContext.telemetry.properties.source = 'funcHostDebugView'; | ||
| for (const folder of vscode.workspace.workspaceFolders ?? []) { | ||
| for (const t of runningFuncTaskMap.getAll(folder)) { | ||
| if (!t) { | ||
| continue; | ||
| } | ||
|
|
||
| if ((t.errorLogs?.length ?? 0) > 0) { | ||
| t.errorLogs = []; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| provider.refresh(); | ||
| }); | ||
|
|
||
| registerCommand('azureFunctions.funcHostDebug.copyRecentLogs', async (actionContext: IActionContext, args: unknown) => { | ||
| actionContext.telemetry.properties.source = 'funcHostDebugView'; | ||
| if (!isHostTaskNode(args)) { | ||
| return; | ||
| } | ||
|
|
||
| const task = runningFuncTaskMap.get(args.workspaceFolder, args.cwd); | ||
| const text = getRecentLogsPlainText(task); | ||
| await vscode.env.clipboard.writeText(text); | ||
| }); | ||
|
|
||
| registerCommand('azureFunctions.funcHostDebug.showRecentLogs', async (actionContext: IActionContext, args: unknown) => { | ||
| actionContext.telemetry.properties.source = 'funcHostDebugView'; | ||
| if (!isHostTaskNode(args)) { | ||
| return; | ||
| } | ||
|
|
||
| const task = runningFuncTaskMap.get(args.workspaceFolder, args.cwd); | ||
| const text = getRecentLogsPlainText(task); | ||
|
|
||
| const doc = await vscode.workspace.openTextDocument({ | ||
| content: text || localize('funcHostDebug.noLogs', 'No logs captured yet.'), | ||
| language: 'log', | ||
| }); | ||
| await vscode.window.showTextDocument(doc, { preview: true }); | ||
| }); | ||
|
|
||
| registerCommand('azureFunctions.funcHostDebug.askCopilot', async (actionContext: IActionContext, args: unknown) => { | ||
| actionContext.telemetry.properties.source = 'funcHostDebugView'; | ||
| if (!isHostErrorNode(args)) { | ||
| return; | ||
| } | ||
|
|
||
| // Use the exact message shown in the error node tooltip. | ||
| const errorContext = stripAnsiControlCharacters(args.message).trim() || args.message; | ||
|
|
||
| const scopeLabel = typeof args.workspaceFolder === 'object' | ||
| ? args.workspaceFolder.name | ||
| : localize('funcHostDebug.globalScope', 'Global'); | ||
|
|
||
| const prompt = [ | ||
| 'I am debugging an Azure Functions project locally in VS Code.', | ||
| `Function Host Port: ${args.portNumber}`, | ||
| `Workspace: ${scopeLabel}`, | ||
| args.cwd ? `CWD: ${args.cwd}` : undefined, | ||
| '', | ||
| 'The Functions host produced an error. Diagnose the likely cause and suggest concrete next steps to fix it.', | ||
| '', | ||
| 'Error output:', | ||
| errorContext, | ||
| ].filter((l): l is string => Boolean(l)).join('\n'); | ||
|
|
||
| await openCopilotChat(prompt); | ||
| }); | ||
|
|
||
| registerCommand('azureFunctions.funcHostDebug.refresh', async (actionContext: IActionContext) => { | ||
| actionContext.telemetry.properties.source = 'funcHostDebugView'; | ||
| provider.refresh(); | ||
| await refreshFuncHostDebugContext(); | ||
| }); | ||
| } |
There was a problem hiding this comment.
The new FuncHostDebugViewProvider class and registerFunctionHostDebugView function lack test coverage. Given that other components in the repository have comprehensive test coverage (as seen in the test directory), these new components should also have tests covering tree item creation, command handlers, error node generation, and the refresh logic.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| export async function openCopilotChat(prompt: string): Promise<void> { | ||
| const trimmed = (prompt ?? '').trim(); | ||
| if (!trimmed) { | ||
| return; | ||
| } | ||
|
|
||
| const candidates: Array<{ command: string; args: unknown[] }> = [ | ||
| // Newer VS Code variants | ||
| { command: 'workbench.action.chat.open', args: [trimmed] }, | ||
| { command: 'workbench.action.chat.open', args: [{ query: trimmed }] }, | ||
| // Older / alternate variants | ||
| { command: 'workbench.action.openChat', args: [trimmed] }, | ||
| // Copilot extensions (IDs vary by version) | ||
| { command: 'github.copilot.openChat', args: [trimmed] }, | ||
| { command: 'github.copilot-chat.openChat', args: [trimmed] }, | ||
| ]; | ||
|
|
||
| for (const { command, args } of candidates) { | ||
| try { | ||
| await vscode.commands.executeCommand(command, ...args); | ||
| return; | ||
| } catch { | ||
| // Ignore and try the next candidate | ||
| } | ||
| } | ||
|
|
||
| void vscode.window.showWarningMessage(localize( | ||
| 'funcHostDebug.copilotChatUnavailable', | ||
| 'Unable to open Copilot Chat. Please ensure GitHub Copilot Chat is installed and enabled.' | ||
| )); | ||
| } |
There was a problem hiding this comment.
The openCopilotChat function lacks test coverage. This function attempts multiple command variants and should be tested to verify it tries all fallbacks and shows the appropriate warning message when all attempts fail.
| const redAnsiRegex = /\u001b\[(?:[0-9;]*31m|[0-9;]*91m|38;5;(9|1)m)/; | ||
|
|
||
| export function isFuncHostErrorLog(log: string): boolean { | ||
| return redAnsiRegex.test(log); |
There was a problem hiding this comment.
The isFuncHostErrorLog function only checks for red ANSI codes, but error logs might not always be colored red (depending on the terminal configuration or the Functions runtime version). Consider adding fallback detection for common error patterns like lines starting with "Error:", "[Error]", "Exception:", etc., to make error detection more robust across different environments.
| return redAnsiRegex.test(log); | |
| // First, prefer detection based on red ANSI color codes (backwards compatible behavior). | |
| if (redAnsiRegex.test(log)) { | |
| return true; | |
| } | |
| // Fallback: detect common error patterns in the plain-text log line (no ANSI/control chars). | |
| const plain = stripAnsiControlCharacters(log).trim(); | |
| if (!plain) { | |
| return false; | |
| } | |
| const lower = plain.toLowerCase(); | |
| // Typical error prefixes from Functions host logs and similar environments. | |
| if (lower.startsWith('error:') || lower.startsWith('[error]') || lower.startsWith('exception:')) { | |
| return true; | |
| } | |
| return false; |
|
@nturinski I've opened a new pull request, #4885, to work on those changes. Once the pull request is ready, I'll request review from you. |
|
@nturinski I've opened a new pull request, #4886, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
|
@nturinski I've opened a new pull request, #4887, to work on those changes. Once the pull request is ready, I'll request review from you. |
…4885) * Initial plan * Add JSDoc documentation for IStartFuncProcessResult interface Co-authored-by: nturinski <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: nturinski <[email protected]>
* Initial plan * Add AbortController to prevent infinite stream iteration Co-authored-by: nturinski <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: nturinski <[email protected]>
|
@nturinski I've opened a new pull request, #4888, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot <[email protected]>
Removed exhaustive check for FuncHostDebugNode.
| const funcCliPath = await getFuncCliPath(context, folder); | ||
| const args = (definition?.args || []) as string[]; | ||
| if (args.length > 0) { | ||
| command = `${command} ${args.join(' ')}`; |
There was a problem hiding this comment.
Since the VSCode Task API accepts argument lists, why not use that?
| const candidates: Array<{ command: string; args: unknown[] }> = [ | ||
| // Newer VS Code variants | ||
| { command: 'workbench.action.chat.open', args: [trimmed] }, | ||
| { command: 'workbench.action.chat.open', args: [{ query: trimmed }] }, | ||
| // Older / alternate variants | ||
| { command: 'workbench.action.openChat', args: [trimmed] }, | ||
| // Copilot extensions (IDs vary by version) | ||
| { command: 'github.copilot.openChat', args: [trimmed] }, | ||
| { command: 'github.copilot-chat.openChat', args: [trimmed] }, | ||
| ]; |
There was a problem hiding this comment.
Does the minimum VSCode version we have in this extension exclude any of these?
|
Very cool, I have it as a todo to review this either tomorrow or early next week! |
| return tasks; | ||
| } | ||
|
|
||
| async function updateFuncHostDebugContext(): Promise<void> { |
There was a problem hiding this comment.
I'm running into an issue on error where the Function Host Debug view is not showing up. Well, at least that's what it appeared like initially. In reality, after I set some breakpoints, I realized that it actually is getting shown, but then immediately gets hidden.
I think for some reason this update function is called to show the view, and then for some reason gets called again and causes the view to hide.
My repro steps were: (1) create a DTS project via createNewProject, (2) hit F5, and (3) answer skip for now so nothing gets set for both connections. This causes this func CLI error which I was expecting to show up:
I should be in office tomorrow so if you're not able to repro maybe we can investigate on my machine, lmk!
This PR adds a Function Host Debug view to the Azure Functions extension. The view appears in the Run and Debug panel and lists running Function Host tasks along with their recent error output.
A new tree view provider backs this view and supplies nodes for active hosts and error entries. The view includes commands to refresh the data, clear recorded errors, view or copy recent logs, and send an error to Copilot. These commands are available from the view toolbar and node context menus.
The PR also introduces a setting to always show the Function Host Debug view, regardless of context.
Supporting changes include exporting shared utilities for identifying host error logs and cleaning log output, extending command handling to accept argument arrays, and updating the Functions task provider to allow additional CLI arguments in task definitions.
No host running (only visible if always show setting is enabled)

Function host running with no errors

Function host running with an error

Location of Ask Copilot context menu
