Skip to content

Added composite command support and test coverage#639

Open
msivasubramaniaan wants to merge 43 commits intoche-incubator:mainfrom
msivasubramaniaan:feat-add-composite-command-support
Open

Added composite command support and test coverage#639
msivasubramaniaan wants to merge 43 commits intoche-incubator:mainfrom
msivasubramaniaan:feat-add-composite-command-support

Conversation

@msivasubramaniaan
Copy link
Collaborator

@msivasubramaniaan msivasubramaniaan commented Jan 28, 2026

What does this PR do?

This PR supports to run the composite commands in terminal

What issues does this PR fix?

eclipse-che/che#21859
eclipse-che/che#23726
eclipse-che/che#23709
eclipse-che/che#23725

How to test this PR?

Added workspace which has all commands of devfile. Run those commands from devfile.yaml.

Does this PR contain changes that override default upstream Code-OSS behavior?

  • the PR contains changes in the code folder (you can skip it if your changes are placed in a che extension )
  • the corresponding items were added to the CHANGELOG.md file
  • rules for automatic git rebase were added to the .rebase folder

Summary by CodeRabbit

  • New Features

    • Added support for composite devfile commands, allowing sequential or parallel execution across components with validation and failure handling
  • Tests

    • Added extensive Jest-based test suites, mocks, and test configs to cover composite execution, task creation, env handling, shell preservation, and edge cases
  • Chores

    • Build images updated to run extension tests during the image build; test tooling and package scripts added for the extension

@github-actions
Copy link

github-actions bot commented Jan 28, 2026

Click here to review and test in web IDE: Contribute

"peerDependencies": {
"jsep": "^0.4.0||^1.0.0"
"node_modules/@emnapi/core": {
"version": "1.8.1",
Copy link
Collaborator

Choose a reason for hiding this comment

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

FYI
The license check is failing

Image

@github-actions
Copy link


// Single-component → join commands
const joiner = parallel ? " & " : " && ";
let compositeCmd = execs.map((e) => e.commandLine).join(joiner);
Copy link
Collaborator

Choose a reason for hiding this comment

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

@RomanNikitenko , is string manipulation the only way to do this ? Using compound tasks on the vscode side still isn't possible ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I investigated that area more than 2 years ago, so I asked @msivasubramaniaan to double check that option when he started to work on the issue.
@msivasubramaniaan could you provide result of your investigation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I double-checked this again.

At the moment, yes — string manipulation is still the only practical way to achieve this.

VS Code does not support dynamically creating or chaining compound tasks programmatically from an extension. Compound tasks must be statically defined in tasks.json, and there’s no API to generate or invoke a compound task on the fly based on resolved components or runtime data.

Because of that limitation, joining commands using && / & (depending on parallel vs sequential execution) remains the only viable solution when the task needs to be assembled dynamically.

So the approach of building a composite command string is the only way

Copy link
Collaborator

@RomanNikitenko RomanNikitenko left a comment

Choose a reason for hiding this comment

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

@msivasubramaniaan
I tried to test the functionality using your samples
I run component-parallel-demo command:

Image

My understanding - it should run signal-frontend in the frontend container and signal-backend in the backend container.

Actual behaviour:

Moreover, the dependent commands just hanging without any output when I run them separately:

Image

Could you clarify:

  • what is expected behaviour there?
  • is it possible to detect which component is used for your test commands?
  • If commands are in the running state - why composite command reports that Composite multi-component execution completed?

thanks in advance!

@RomanNikitenko
Copy link
Collaborator

@msivasubramaniaan
I've prepared own sample and tested parallel and sequential uses cases for 2 components, I can confirm - it does not work, there is a bug - no output for both dependent commands and it's unclear if the commands are executed at all:

Screenshot 2026-02-02 at 23 17 53

@RomanNikitenko
Copy link
Collaborator

@msivasubramaniaan
I've detected one more bug : a command label should be displayed instead of id:

Screenshot 2026-02-02 at 23 29 09 Screenshot 2026-02-02 at 23 34 23

@msivasubramaniaan msivasubramaniaan marked this pull request as draft February 5, 2026 11:44
@RomanNikitenko
Copy link
Collaborator

RomanNikitenko commented Feb 5, 2026

@msivasubramaniaan
I see you've requested a new review, but image for testing is not ready.
Please let me know when you test all use cases we discussed and regressions are fixed.
Thanks!

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

@github-actions
Copy link

@msivasubramaniaan
Copy link
Collaborator Author

How it can be used effectively:

  1. The aggregated output is tagged by component, for example:
[backend] build started
[frontend] build started
[backend] compiling...
[frontend] bundling...
[backend] done
[frontend] done
  1. This makes it possible to identify which component produced each log line.

Sorry - but in the current implementation there is no aggregated output is tagged by component So - it's not true: This makes it possible to identify which component produced each log line.

@msivasubramaniaan I understand that there is a technical issue and you are trying to workaround it but if you would have a choice - would you use the mixed output or separate?

my personal opinion - there are already separate terminals for each dependent task with the corresponding output - I would not use mixed output in the third terminal...

@RomanNikitenko
Here is the reference of Parallel cross component used only equal no of terminals for the list of commands.
image

Also the inconsistency behavior issue also fixed and removed the unnecessary logs as well

@github-actions
Copy link


vscode.tasks.executeTask(childTask);
});
closeEmitter.fire(0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The most critical use case is support for Parallel Composite tasks.
In my view, the current implementation would be the best one:

  • it is aligned with the VS Code behavior for compound tasks
  • dependent tasks run in their own terminals
  • there is no mixed output in a single terminal (as in the previous implementation)
  • there are no extra terminals: number of terminals = number of dependent tasks (vs the previous approach, where it was number of dependent tasks + 1 parent terminal)

Unfortunately, the current approach has one significant drawback: if I understand correctly - actually a parent (extra) terminal is still created and then immediately closed. It looks like a bug on UI.

Screen.Recording.2026-02-27.at.11.29.26.mov

@RomanNikitenko
Copy link
Collaborator

RomanNikitenko commented Feb 27, 2026

@msivasubramaniaan
One more use case I detected is:

  • let's say there is a classical build and run sequential command
  • current behavior: the second command runs even when the first one fails
  • I expect: run command doesn't start - it does not make sense to run it when the previous one (build) fails
  • I checked that behavior for the vanilla VS Code compound task - VS Code doesn't start the second command if the first one fails

Please see on the video 2 behaviors:

  • the first one - for the che-code composite task
  • second one - for the vanilla VS Code compound task
build_and_run_bug.mov

@RomanNikitenko
Copy link
Collaborator

RomanNikitenko commented Feb 27, 2026

@msivasubramaniaan
Another problem for the sequential composite command support:

  • first command fails but second still runs
  • composite item is displayed as successful one
  • it's possible to detect that something went wrong only searching the corresponding logs
Screenshot 2026-02-27 at 14 06 55

Update: it does not matter first command fails or the second one - command item has incorrect status...
On the screenshot bellow the second command failed

Screenshot 2026-02-27 at 14 33 45

VS Code behavior, for example:

Screenshot 2026-02-27 at 14 10 07

@github-actions
Copy link

1 similar comment
@github-actions
Copy link

@msivasubramaniaan
Copy link
Collaborator Author

msivasubramaniaan commented Mar 2, 2026

Hello @RomanNikitenko
As discussed completed all the feedbacks other than #639 (comment). AFAIK VS Code requires a task to have an execution to run, so I just created a task and close it immediately. If any other suggestion I am glad to receive it.

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

}

private sanitize(cmd: string) {
return cmd.replace(/(?:\s*(?:&&|\|\||[|;&]))+\s*$/, "").trim();
Copy link
Collaborator

Choose a reason for hiding this comment

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

@msivasubramaniaan
I guess this logic changes behavior from background/non-blocking to foreground/blocking, which is a semantic regression.
Like: cmd & becomes cmd

Could you double check it using logs or another way?

I prepared simple commands that show a background command blocks next command

BG_command.mov

Copy link
Collaborator

@RomanNikitenko RomanNikitenko Mar 12, 2026

Choose a reason for hiding this comment

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

@msivasubramaniaan
For example, we use background command to run machine-exec in the che-code: https://github.com/che-incubator/che-code/blob/main/build/scripts/entrypoint-volume.sh#L74

Copy link
Collaborator

Choose a reason for hiding this comment

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

I used the following commands for testing:

- id: bg-command
    exec:
      label: "Background Command"
      component: ubi9-tools
      commandLine: "sh -c 'echo Start Background Task; sleep 5; echo Complete Background Task' &"
  - id: after-bg-command
    exec:
      label: "NEXT Task after BG Command"
      component: ubi9-tools
      commandLine: "sh -c 'sleep 1; echo NEXT Task after BG TASK'"
  - id: bg-in-composite
    composite:
      label: "Background operator stripped"
      commands:
        - bg-command
        - after-bg-command
      parallel: false

@RomanNikitenko
Copy link
Collaborator

@msivasubramaniaan
I detected another bug, it's related to resolving workingDir.

On the video you can see that:

  • resolving workingDir works correctly when I run separate tasks
  • there is a bug when I run the same tasks within a composite task
Working_Dir_bug.mp4

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

Adds Jest testing infra and tests for the che-commands extension, implements a CompositeTaskBuilder for devfile composite commands, updates task provider to use it, adjusts TypeScript configs and package.json for testing, and runs extension tests during Docker image builds.

Changes

Cohort / File(s) Summary
Docker Build Pipeline
build/dockerfiles/linux-libc-ubi8.Dockerfile, build/dockerfiles/linux-libc-ubi9.Dockerfile, build/dockerfiles/linux-musl.Dockerfile
Run npm --prefix extensions/che-commands test after dependency reinstall and before main build steps; update copyright year ranges.
Jest & TypeScript Test Config
code/extensions/che-commands/jest.config.js, code/extensions/che-commands/tsconfig.jest.json, code/extensions/che-commands/tsconfig.json
Add Jest config (node env, roots, coverage, ts-jest transform, moduleNameMapper) and test-specific tsconfig; enable declaration and include tests/**/*.ts; add jest types.
Package Changes
code/extensions/che-commands/package.json
Add test script (jest) and devDependencies: jest, ts-jest, @types/jest (plus minor formatting).
Composite Task Feature
code/extensions/che-commands/src/compositeTaskBuilder.ts
New CompositeTaskBuilder class: validates/flattens composite devfile commands, detects cycles/missing refs, and builds sequential or parallel vscode.Tasks using pseudoterminal or per-step exec strategies.
Task Provider Integration
code/extensions/che-commands/src/taskProvider.ts
Instantiate and use CompositeTaskBuilder for composite commands; only create tasks for execs with runnable command lines; fix env escaping using regex.
VS Code Mocks & Test Utilities
code/extensions/che-commands/tests/__mocks__/vscode.ts, code/extensions/che-commands/tests/mocks.ts
Add lightweight VS Code API mocks and MockTerminal/MockPty/MockCheAPI utilities to simulate PTY behavior, command output, exit codes, and devfile service responses.
Tests
code/extensions/che-commands/tests/compositeTaskBuilder.test.ts, code/extensions/che-commands/tests/taskProvider.test.ts
Add comprehensive Jest suites covering composite flattening, sequential/parallel execution semantics, failure handling, cycle/missing-ref validation, command preservation, env/workingDir expansion, and exec task creation.

Sequence Diagram

sequenceDiagram
    participant Client as Client / Extension
    participant TaskProvider as DevfileTaskProvider
    participant CompBuilder as CompositeTaskBuilder
    participant Terminal as Pseudo-Terminal
    participant Component as Workspace Component

    Client->>TaskProvider: provideTasks(devfile)
    activate TaskProvider
    TaskProvider->>CompBuilder: build(composite, allCommands)
    activate CompBuilder
    CompBuilder->>CompBuilder: validate & flatten composite graph
    alt single-component composite
        CompBuilder->>Terminal: create PTY that runs concatenated commands
        activate Terminal
        Terminal->>Component: execute command(s)
        Component-->>Terminal: output / exit code
        deactivate Terminal
    else multi-component composite
        CompBuilder->>Terminal: create PTY that starts per-step exec tasks
        activate Terminal
        Terminal->>Component: for each step: execute via machine exec or tasks.executeTask
        Component-->>Terminal: outputs
        deactivate Terminal
    end
    CompBuilder-->>TaskProvider: return vscode.Task
    deactivate CompBuilder
    TaskProvider-->>Client: Task[]
    deactivate TaskProvider

    Client->>Terminal: execute task
    activate Terminal
    Terminal->>Component: run underlying commands
    Component-->>Terminal: output / exit
    Terminal-->>Client: task complete
    deactivate Terminal
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I flattened composites with nimble paws,

Jest hopped in to check all laws,
Mocks whispered secrets to PTY ears,
Docker now runs our new tests for cheers,
A rabbit's wink — the extension nears!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: it adds composite command support and test coverage, which are the primary objectives reflected in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Tip

You can disable poems in the walkthrough.

Disable the reviews.poem setting to disable the poems in the walkthrough.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
code/extensions/che-commands/src/compositeTaskBuilder.ts (1)

328-330: ⚠️ Potential issue | 🟠 Major

Background operator & at end of command is stripped, changing semantics.

The sanitize() regex removes trailing & which converts background commands to foreground. For example, cmd & becomes cmd. This was flagged in past reviews - a command intended to run in background will now block.

🐛 Proposed fix: only strip joiners when combining commands
-	private sanitize(cmd: string) {
-		return cmd.replace(/(?:\s*(?:&&|\|\||[|;&]))+\s*$/, "").trim();
+	private sanitize(cmd: string) {
+		// Only strip trailing sequential joiners (&&, ||, ;) - preserve background &
+		return cmd.replace(/(?:\s*(?:&&|\|\||;))+\s*$/, "").trim();
 	}

Note: This preserves standalone & for backgrounding while still removing trailing &&, ||, and ; that are meant as joiners.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts` around lines 328 -
330, sanitize currently strips a trailing single ampersand (background operator)
because the regex includes '&' in the character class; update the sanitize
function so it only strips command joiners (&&, ||, |, ;) but preserves a
standalone '&' at the end; specifically, replace the current regex in sanitize
with one that does not include '&' in the single-character class (e.g., remove
'&' from [|;&] and keep matching multi-char operators like && and ||) so
sanitize(cmd: string) returns the trimmed string but leaves a trailing single
'&' intact.
🧹 Nitpick comments (6)
code/extensions/che-commands/package.json (1)

82-82: Missing trailing newline at end of file.

Most editors and linters expect a trailing newline. Consider adding one for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/package.json` at line 82, The file
code/extensions/che-commands/package.json is missing a trailing newline after
the final closing brace; open that package.json and add a single newline
character at the end of file so the file ends with "\n" (ensure the final "}"
remains intact), save the file and include the change in your commit so
linters/editors recognize the EOF newline.
code/extensions/che-commands/src/compositeTaskBuilder.ts (1)

187-213: Consider extracting duplicate cancellation logic.

The close() and handleInput('\x03') handlers share nearly identical cleanup logic. Extract to a helper for maintainability.

♻️ Suggested refactor
+			const cancelExecution = () => {
+				isCancelled = true;
+				for (const p of activePtys) {
+					try {
+						p.handleInput?.("\x03");
+						p.close?.();
+					} catch {}
+				}
+				activePtys.clear();
+				closeEmitter.fire(130);
+			};
+
 			const aggregator: vscode.Pseudoterminal = {
 				// ...
 				close: () => {
-					isCancelled = true;
-					for (const p of activePtys) {
-						try {
-							p.handleInput?.("\x03");
-							p.close?.();
-						} catch {}
-					}
-					activePtys.clear();
-					closeEmitter.fire(130);
+					cancelExecution();
 				},
 				handleInput: (data: string) => {
 					if (data === "\x03") {
-						isCancelled = true;
-						for (const p of activePtys) {
-							p.handleInput?.("\x03");
-							p.close?.();
-						}
-						activePtys.clear();
-						closeEmitter.fire(130);
+						cancelExecution();
 					}
 				},
 			};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts` around lines 187 -
213, The cancellation/cleanup logic duplicated in the compositeTaskBuilder
object's close() and handleInput(data) when data === "\x03" should be extracted
into a single helper function (e.g., cancelAndCleanup or performCancellation) to
avoid repetition: move the body that sets isCancelled = true, iterates
activePtys calling p.handleInput?.("\x03") and p.close?.(), clears activePtys,
and fires closeEmitter.fire(130) into that helper, then call that helper from
both close and handleInput branches; update references to activePtys,
isCancelled, and closeEmitter inside the new helper so behavior remains
identical.
code/extensions/che-commands/tests/__mocks__/vscode.ts (1)

30-55: Consider adding TaskRevealKind, TaskPanelKind, and tasks to the mock.

The compositeTaskBuilder.ts uses vscode.TaskRevealKind, vscode.TaskPanelKind, and vscode.tasks.executeTask, which are currently mocked inline in tests. Adding them here would improve consistency and reduce test-level mocking.

♻️ Optional additions to the mock
 export const TaskScope = {
 	Workspace: 1,
 };
+
+export const TaskRevealKind = {
+	Always: 1,
+	Silent: 2,
+	Never: 3,
+};
+
+export const TaskPanelKind = {
+	Shared: 1,
+	Dedicated: 2,
+	New: 3,
+};
+
+export const tasks = {
+	executeTask: async () => ({ terminate: () => {} }),
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/tests/__mocks__/vscode.ts` around lines 30 - 55,
The mock is missing TaskRevealKind, TaskPanelKind, and vscode.tasks.executeTask
used by compositeTaskBuilder.ts; add TaskRevealKind and TaskPanelKind
enums/objects (e.g., values like Always/Hidden/never and
Shared/Dedicated/Replace) alongside the existing Task and TaskScope, and add a
tasks object exposing an executeTask method that returns a Promise (can resolve
to undefined) and a registerTaskProvider stub; ensure the symbols
TaskRevealKind, TaskPanelKind, and tasks.executeTask are exported from the mock
so tests can use them consistently.
code/extensions/che-commands/tests/mocks.ts (1)

45-47: close() always fires exit code 0, ignoring configured exitCode.

When close() is called directly (e.g., during cancellation), it fires 0 instead of the configured exit code. This may not matter for current tests but could cause confusion if tests expect close() to respect configured exit codes.

♻️ Consider using configured exitCode or a parameter
-	close() {
-		for (const l of this.closeListeners) l(0);
+	close(code?: number) {
+		for (const l of this.closeListeners) l(code ?? this.exitCode);
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/tests/mocks.ts` around lines 45 - 47, The
close() method currently always invokes closeListeners with 0; update it to use
the configured exit code (e.g., this.exitCode) or accept an optional parameter
so it calls each listener with the real exit code instead of hardcoding 0;
modify the close() signature and callers (or default to this.exitCode) and
ensure closeListeners is invoked with that exit code value so cancellation paths
and tests receive the correct code (refer to close(), closeListeners and any
exitCode property/field).
code/extensions/che-commands/tests/taskProvider.test.ts (1)

143-166: Cleanup process.env.TEST_DIR after test to avoid pollution.

The test sets process.env.TEST_DIR without cleanup, which could affect other tests.

♻️ Suggested fix
 describe("Exec working directory expansion", () => {
+	const originalTestDir = process.env.TEST_DIR;
+
+	afterEach(() => {
+		if (originalTestDir === undefined) {
+			delete process.env.TEST_DIR;
+		} else {
+			process.env.TEST_DIR = originalTestDir;
+		}
+	});
+
 	test("expands ${VAR} placeholders from process.env", async () => {
 		process.env.TEST_DIR = "/tmp/demo";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/tests/taskProvider.test.ts` around lines 143 -
166, The test "expands ${VAR} placeholders from process.env" sets
process.env.TEST_DIR but never restores it; either restore the original value or
delete process.env.TEST_DIR after the test to avoid pollution. Update the test
(or add an afterEach) around the test that calls
provide/runFirst/MockTerminalAPI so you capture the previous value of
process.env.TEST_DIR, run the assertions, and then set process.env.TEST_DIR back
to the previous value (or delete it) in a finally/cleanup step to ensure other
tests aren’t affected.
code/extensions/che-commands/tests/compositeTaskBuilder.test.ts (1)

117-153: Test isolation: restore mocked vscode properties after test.

The test mutates the global vscode mock (TaskRevealKind, TaskPanelKind, tasks) without cleanup. This can pollute subsequent tests in the same suite.

♻️ Suggested fix using beforeEach/afterEach
+describe("Composite — cross component execution", () => {
+	let originalTaskRevealKind: any;
+	let originalTaskPanelKind: any;
+	let originalTasks: any;
+
+	beforeEach(() => {
+		originalTaskRevealKind = (vscode as any).TaskRevealKind;
+		originalTaskPanelKind = (vscode as any).TaskPanelKind;
+		originalTasks = (vscode as any).tasks;
+	});
+
+	afterEach(() => {
+		(vscode as any).TaskRevealKind = originalTaskRevealKind;
+		(vscode as any).TaskPanelKind = originalTaskPanelKind;
+		(vscode as any).tasks = originalTasks;
+	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/tests/compositeTaskBuilder.test.ts` around lines
117 - 153, The test "parallel composite executes all components and opens only
child terminals" mutates global vscode properties (TaskRevealKind,
TaskPanelKind, tasks) and should restore them to avoid polluting other tests;
modify the test file to save the original values of (vscode as
any).TaskRevealKind, (vscode as any).TaskPanelKind and (vscode as any).tasks
before mutating and restore them in an afterEach (or restore at the end of the
test), or move the setup into a beforeEach and the teardown into afterEach so
provide, MockTerminalAPI and runByName behavior remains isolated; locate the
test by its name and update the surrounding describe block to include
beforeEach/afterEach that capture/restore those symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/extensions/che-commands/jest.config.js`:
- Around line 19-23: The current jest config uses the deprecated
globals['ts-jest'] and likely a preset; replace this by removing the preset and
moving the ts-jest settings into the transform option: remove the globals object
(and any preset entry), add a transform mapping for the TypeScript files that
references ts-jest and passes the tsconfig.jest.json path (preserving the same
tsconfig). Update any references to globals or preset so Jest uses transform
with ts-jest instead of globals['ts-jest'].

In `@code/extensions/che-commands/tsconfig.json`:
- Line 10: Remove test-specific types and include patterns from the main
tsconfig: delete "jest" from the "types" array and remove the "tests/**/*.ts"
(or any test globs) from "include" in the existing tsconfig.json; instead ensure
the dedicated tsconfig.jest.json contains those entries and is used for test
runs. Locate the "types": ["jest"] reference and any "tests/**/*.ts" include
entries in the tsconfig.json and remove them, and verify tsconfig.jest.json
contains the test-specific "types" and "include" so test files and Jest types
are only declared there.

---

Duplicate comments:
In `@code/extensions/che-commands/src/compositeTaskBuilder.ts`:
- Around line 328-330: sanitize currently strips a trailing single ampersand
(background operator) because the regex includes '&' in the character class;
update the sanitize function so it only strips command joiners (&&, ||, |, ;)
but preserves a standalone '&' at the end; specifically, replace the current
regex in sanitize with one that does not include '&' in the single-character
class (e.g., remove '&' from [|;&] and keep matching multi-char operators like
&& and ||) so sanitize(cmd: string) returns the trimmed string but leaves a
trailing single '&' intact.

---

Nitpick comments:
In `@code/extensions/che-commands/package.json`:
- Line 82: The file code/extensions/che-commands/package.json is missing a
trailing newline after the final closing brace; open that package.json and add a
single newline character at the end of file so the file ends with "\n" (ensure
the final "}" remains intact), save the file and include the change in your
commit so linters/editors recognize the EOF newline.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts`:
- Around line 187-213: The cancellation/cleanup logic duplicated in the
compositeTaskBuilder object's close() and handleInput(data) when data === "\x03"
should be extracted into a single helper function (e.g., cancelAndCleanup or
performCancellation) to avoid repetition: move the body that sets isCancelled =
true, iterates activePtys calling p.handleInput?.("\x03") and p.close?.(),
clears activePtys, and fires closeEmitter.fire(130) into that helper, then call
that helper from both close and handleInput branches; update references to
activePtys, isCancelled, and closeEmitter inside the new helper so behavior
remains identical.

In `@code/extensions/che-commands/tests/__mocks__/vscode.ts`:
- Around line 30-55: The mock is missing TaskRevealKind, TaskPanelKind, and
vscode.tasks.executeTask used by compositeTaskBuilder.ts; add TaskRevealKind and
TaskPanelKind enums/objects (e.g., values like Always/Hidden/never and
Shared/Dedicated/Replace) alongside the existing Task and TaskScope, and add a
tasks object exposing an executeTask method that returns a Promise (can resolve
to undefined) and a registerTaskProvider stub; ensure the symbols
TaskRevealKind, TaskPanelKind, and tasks.executeTask are exported from the mock
so tests can use them consistently.

In `@code/extensions/che-commands/tests/compositeTaskBuilder.test.ts`:
- Around line 117-153: The test "parallel composite executes all components and
opens only child terminals" mutates global vscode properties (TaskRevealKind,
TaskPanelKind, tasks) and should restore them to avoid polluting other tests;
modify the test file to save the original values of (vscode as
any).TaskRevealKind, (vscode as any).TaskPanelKind and (vscode as any).tasks
before mutating and restore them in an afterEach (or restore at the end of the
test), or move the setup into a beforeEach and the teardown into afterEach so
provide, MockTerminalAPI and runByName behavior remains isolated; locate the
test by its name and update the surrounding describe block to include
beforeEach/afterEach that capture/restore those symbols.

In `@code/extensions/che-commands/tests/mocks.ts`:
- Around line 45-47: The close() method currently always invokes closeListeners
with 0; update it to use the configured exit code (e.g., this.exitCode) or
accept an optional parameter so it calls each listener with the real exit code
instead of hardcoding 0; modify the close() signature and callers (or default to
this.exitCode) and ensure closeListeners is invoked with that exit code value so
cancellation paths and tests receive the correct code (refer to close(),
closeListeners and any exitCode property/field).

In `@code/extensions/che-commands/tests/taskProvider.test.ts`:
- Around line 143-166: The test "expands ${VAR} placeholders from process.env"
sets process.env.TEST_DIR but never restores it; either restore the original
value or delete process.env.TEST_DIR after the test to avoid pollution. Update
the test (or add an afterEach) around the test that calls
provide/runFirst/MockTerminalAPI so you capture the previous value of
process.env.TEST_DIR, run the assertions, and then set process.env.TEST_DIR back
to the previous value (or delete it) in a finally/cleanup step to ensure other
tests aren’t affected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c09384de-6585-4a52-b536-95dd5db8d961

📥 Commits

Reviewing files that changed from the base of the PR and between 4bab97c and 8d6f68f.

⛔ Files ignored due to path filters (1)
  • code/extensions/che-commands/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (13)
  • build/dockerfiles/linux-libc-ubi8.Dockerfile
  • build/dockerfiles/linux-libc-ubi9.Dockerfile
  • build/dockerfiles/linux-musl.Dockerfile
  • code/extensions/che-commands/jest.config.js
  • code/extensions/che-commands/package.json
  • code/extensions/che-commands/src/compositeTaskBuilder.ts
  • code/extensions/che-commands/src/taskProvider.ts
  • code/extensions/che-commands/tests/__mocks__/vscode.ts
  • code/extensions/che-commands/tests/compositeTaskBuilder.test.ts
  • code/extensions/che-commands/tests/mocks.ts
  • code/extensions/che-commands/tests/taskProvider.test.ts
  • code/extensions/che-commands/tsconfig.jest.json
  • code/extensions/che-commands/tsconfig.json

"declaration": true,
"types": [
"node",
"jest",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if test files are compiled to the out directory during build
# and if the webpack config processes tsconfig.json

echo "=== Checking webpack config usage of tsconfig.json ==="
rg -n "tsconfig.json" code/extensions/shared.webpack.config.mjs code/extensions/mangle-loader.js

echo ""
echo "=== Checking if tests directory exists and what it contains ==="
fd -t f . code/extensions/che-commands/tests 2>/dev/null | head -20

echo ""
echo "=== Checking gulp compile task for che-commands ==="
rg -n "che-commands" code/build/gulpfile.extensions.js 2>/dev/null || echo "No specific gulp task found"

Repository: che-incubator/che-code

Length of output: 758


🏁 Script executed:

#!/bin/bash

echo "=== Checking for tsconfig.jest.json ==="
find code/extensions/che-commands -name "tsconfig.jest.json" -o -name "tsconfig.*.json" | head -20

echo ""
echo "=== Examining shared.webpack.config.mjs to understand tsconfig usage ==="
sed -n '40,160p' code/extensions/shared.webpack.config.mjs

echo ""
echo "=== Checking current tsconfig.json include array ==="
cat code/extensions/che-commands/tsconfig.json | grep -A 10 '"include"'

Repository: che-incubator/che-code

Length of output: 4833


🏁 Script executed:

#!/bin/bash

echo "=== Checking tsLoaderOptions definition ==="
rg -B 5 "tsLoaderOptions" code/extensions/shared.webpack.config.mjs | head -30

echo ""
echo "=== Examining mangle-loader.js usage of configFile ==="
cat code/extensions/mangle-loader.js | head -50

echo ""
echo "=== Checking if tsconfig.jest.json extends main tsconfig ==="
cat code/extensions/che-commands/tsconfig.jest.json

Repository: che-incubator/che-code

Length of output: 2606


Test files and Jest types should not be in the main tsconfig.json.

While the CopyWebpackPlugin prevents test files from being included in the production output, including "jest" in types and "tests/**/*.ts" in the main tsconfig still pollutes the production type-checking environment. The dedicated tsconfig.jest.json already exists and should be the single source of truth for all test-related configuration.

Proposed fix
 	"compilerOptions": {
 		"outDir": "./out",
 		"experimentalDecorators": true,
 		"emitDecoratorMetadata": true,
-		"declaration": true,
 		"types": [
 			"node",
-			"jest",
 			"reflect-metadata"
 		]
 	},
 	"include": [
 		"src/**/*",
-		"tests/**/*.ts",
 		"../../src/vscode-dts/vscode.d.ts"
 	]
📝 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
"jest",
"compilerOptions": {
"outDir": "./out",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"types": [
"node",
"reflect-metadata"
]
},
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts"
]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/tsconfig.json` at line 10, Remove test-specific
types and include patterns from the main tsconfig: delete "jest" from the
"types" array and remove the "tests/**/*.ts" (or any test globs) from "include"
in the existing tsconfig.json; instead ensure the dedicated tsconfig.jest.json
contains those entries and is used for test runs. Locate the "types": ["jest"]
reference and any "tests/**/*.ts" include entries in the tsconfig.json and
remove them, and verify tsconfig.jest.json contains the test-specific "types"
and "include" so test files and Jest types are only declared there.

@github-actions
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/extensions/che-commands/jest.config.js`:
- Around line 14-28: Replace the multiple named exports (testEnvironment, roots,
collectCoverage, transform, moduleNameMapper, collectCoverageFrom) with a single
exported Jest configuration object (e.g., module.exports = { ... } or export
default { ... }) that contains those keys and values; keep the existing values
(including the '^.+\\.tsx?$' transform using ts-jest and its tsconfig setting,
the moduleNameMapper for '^vscode$', and the collectCoverageFrom array) but
remove the individual export const declarations and return them as properties of
one config object.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts`:
- Around line 43-45: The code currently turns an empty/unresolved composite into
a no-op echo task by returning this.echoTask(`Composite ${command.id} resolved
empty`); instead of skipping it, change the logic to return undefined (or null)
when execs.length === 0 so DevfileTaskProvider.computeTasks() can filter it out;
update the same pattern found around lines where the alternate branch returns an
echo task (referencing this.echoTask and the composite task building function in
compositeTaskBuilder.ts) and ensure callers expect a possibly undefined task
rather than a placeholder echo.
- Around line 88-90: The composite PTY emits duplicate close events because both
the child PTY exit handler and the final open() success path call
closeEmitter.fire; modify the CustomExecution created in compositeTaskBuilder.ts
so closeEmitter.fire is invoked exactly once by adding a single shared "closed"
flag (or using an atomic/once guard) checked/updated before firing; update the
open() finalization path and any child PTY exit/CTRL+C handlers (the places that
currently call closeEmitter.fire with exit code 130 and the final success close)
to consult this guard and only fire when it transitions from false to true, and
ensure any related disposables/listeners clear or ignore further close attempts.
- Around line 61-67: Composite exec commands are bypassing the normal standalone
path (createCheTask) and call getMachineExecPTY directly, causing env to be
dropped, sanitize() to be skipped and workingDir to differ; update the composite
branches that build exec tasks (places that use cmd.exec.commandLine and call
getMachineExecPTY) to instead construct tasks via createCheTask(...) passing
through cmd.exec.env and resolved workingDir (use cmd.exec.workingDir ||
"${PROJECT_SOURCE}"), and ensure the commandLine is run through sanitize() (or
the same sanitizer used by createCheTask) so composite, sequential and parallel
branches behave identically; apply the same change to the other
composite-building occurrences that mirror this pattern.
- Around line 190-207: The composite PTY currently fires closeEmitter.fire(0)
immediately after launching children; change it to capture the TaskExecution
returned by vscode.tasks.executeTask(childTask) inside the open() of the
CustomExecution (where execs.forEach and buildExecTask(...) are invoked), track
those executions and wait for their termination via
vscode.tasks.onDidEndTaskProcess or vscode.tasks.onDidEndTask, and only call
closeEmitter.fire(0) when all tracked child TaskExecution instances have
completed; also wire parent close() to cancel/terminate any still-running
TaskExecution handles to support proper cancellation and cleanup.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8456d5f7-14d6-434c-a3c9-ddc4e2bce433

📥 Commits

Reviewing files that changed from the base of the PR and between 8d6f68f and 008820f.

📒 Files selected for processing (3)
  • code/extensions/che-commands/jest.config.js
  • code/extensions/che-commands/src/compositeTaskBuilder.ts
  • code/extensions/che-commands/tests/compositeTaskBuilder.test.ts

Comment on lines +14 to +28
export const testEnvironment = 'node';
export const roots = ['<rootDir>/tests'];
export const collectCoverage = true;
export const transform = {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: 'tsconfig.json' }],
};
export const moduleNameMapper = {
'^vscode$': '<rootDir>/tests/__mocks__/vscode.ts'
};
export const collectCoverageFrom = [
"src/**/*.ts",
"!src/**/*.d.ts",
"!tests/**",
] No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Jest's official configuration docs, should jest.config.js export the configuration object itself (module.exportsorexport default) instead of named exports?

💡 Result:

Yes, in Jest's official configuration docs, jest.config.js should export the configuration object itself using module.exports = config (for CommonJS JavaScript) or export default config (for ES modules or TypeScript), rather than named exports. The docs explicitly state: "The configuration file should simply export an object," and provide examples using these default export patterns.

Citations:


Export the Jest configuration as a single object, not named exports.

The jest.config.js file currently exports individual named bindings (export const testEnvironment, export const roots, etc.), but Jest requires the configuration to be exported as a single object via module.exports or export default. The runner will fail to load these settings before tests run.

Proposed fix
 //@ts-check
-export const testEnvironment = 'node';
-export const roots = ['<rootDir>/tests'];
-export const collectCoverage = true;
-export const transform = {
-  '^.+\\.tsx?$': ['ts-jest', { 
-    tsconfig: 'tsconfig.json' }],
-};
-export const moduleNameMapper = {
-    '^vscode$': '<rootDir>/tests/__mocks__/vscode.ts'
-};
-export const collectCoverageFrom = [
-    "src/**/*.ts",
-    "!src/**/*.d.ts",
-    "!tests/**",
-]
+/** `@type` {import('jest').Config} */
+module.exports = {
+  testEnvironment: 'node',
+  roots: ['<rootDir>/tests'],
+  collectCoverage: true,
+  transform: {
+    '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
+  },
+  moduleNameMapper: {
+    '^vscode$': '<rootDir>/tests/__mocks__/vscode.ts',
+  },
+  collectCoverageFrom: [
+    'src/**/*.ts',
+    '!src/**/*.d.ts',
+    '!tests/**',
+  ],
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/jest.config.js` around lines 14 - 28, Replace
the multiple named exports (testEnvironment, roots, collectCoverage, transform,
moduleNameMapper, collectCoverageFrom) with a single exported Jest configuration
object (e.g., module.exports = { ... } or export default { ... }) that contains
those keys and values; keep the existing values (including the '^.+\\.tsx?$'
transform using ts-jest and its tsconfig setting, the moduleNameMapper for
'^vscode$', and the collectCoverageFrom array) but remove the individual export
const declarations and return them as properties of one config object.

Comment on lines +43 to +45
if (!execs.length) {
return this.echoTask(`Composite ${command.id} resolved empty`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Skip empty composites instead of surfacing a fake echo task.

Line 44 turns an unresolved/empty composite into a runnable task. Because DevfileTaskProvider.computeTasks() only filters undefined, that no-op still shows up in the task list and the “invalid composites are not exposed” behavior can pass just because the task was renamed.

💡 Proposed fix
-		if (!execs.length) {
-			return this.echoTask(`Composite ${command.id} resolved empty`);
-		}
+		if (!execs.length) {
+			this.channel.appendLine(`Skipping composite ${command.id}: resolved empty`);
+			return undefined;
+		}

Also applies to: 291-299

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts` around lines 43 -
45, The code currently turns an empty/unresolved composite into a no-op echo
task by returning this.echoTask(`Composite ${command.id} resolved empty`);
instead of skipping it, change the logic to return undefined (or null) when
execs.length === 0 so DevfileTaskProvider.computeTasks() can filter it out;
update the same pattern found around lines where the alternate branch returns an
echo task (referencing this.echoTask and the composite task building function in
compositeTaskBuilder.ts) and ensure callers expect a possibly undefined task
rather than a placeholder echo.

Comment on lines +61 to +67
if (cmd.exec?.commandLine) {
result.push({
command: cmd.exec.commandLine,
workdir: cmd.exec.workingDir || "${PROJECT_SOURCE}",
component: cmd.exec.component,
env: cmd.exec.env,
label: cmd.exec.label,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Route composite leaves through the same exec-task path as standalone commands.

flatten() keeps env, the sequential branch sanitizes commands, and standalone exec tasks still go through createCheTask(...); both composite branches bypass that by calling getMachineExecPTY(...) directly. That makes the same command behave differently inside a composite: env is dropped, workingDir handling can drift from standalone tasks, and the parallel branch skips sanitize(). The workingDir regression called out in the PR discussion is a likely symptom of this split.

Also applies to: 99-103, 241-257, 273-275

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts` around lines 61 -
67, Composite exec commands are bypassing the normal standalone path
(createCheTask) and call getMachineExecPTY directly, causing env to be dropped,
sanitize() to be skipped and workingDir to differ; update the composite branches
that build exec tasks (places that use cmd.exec.commandLine and call
getMachineExecPTY) to instead construct tasks via createCheTask(...) passing
through cmd.exec.env and resolved workingDir (use cmd.exec.workingDir ||
"${PROJECT_SOURCE}"), and ensure the commandLine is run through sanitize() (or
the same sanitizer used by createCheTask) so composite, sequential and parallel
branches behave identically; apply the same change to the other
composite-building occurrences that mirror this pattern.

Comment on lines +88 to +90
const execution = new vscode.CustomExecution(async () => {
const writeEmitter = new vscode.EventEmitter<string>();
const closeEmitter = new vscode.EventEmitter<number>();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Close the sequential PTY exactly once.

When Ctrl+C or close() emits 130, open() can still fall through to the final success close after the active child PTY exits. That produces duplicate close events and can make a cancelled composite look successful.

💡 Proposed fix
 		const execution = new vscode.CustomExecution(async () => {
 			const writeEmitter = new vscode.EventEmitter<string>();
 			const closeEmitter = new vscode.EventEmitter<number>();
+			let closed = false;
+			const finalize = (exitCode: number) => {
+				if (closed) return;
+				closed = true;
+				closeEmitter.fire(exitCode);
+			};

 			const aggregator: vscode.Pseudoterminal = {
 				onDidWrite: writeEmitter.event,
 				onDidClose: closeEmitter.event,
@@
 					for (const e of execs) {
 						if (isCancelled) break;
 						const exitCode: number = await run(e);
 						if(exitCode !== 0) {
 							isCancelled = true;
-							closeEmitter.fire(exitCode);
+							finalize(exitCode);
 							return;
 						}
 					}
-					closeEmitter.fire(0);
+					finalize(isCancelled ? 130 : 0);
 				},
@@
 					activePtys.clear();
-					closeEmitter.fire(130);
+					finalize(130);
 				},
@@
 						activePtys.clear();
-						closeEmitter.fire(130);
+						finalize(130);
 					}
 				},

Also applies to: 125-148, 151-162

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts` around lines 88 -
90, The composite PTY emits duplicate close events because both the child PTY
exit handler and the final open() success path call closeEmitter.fire; modify
the CustomExecution created in compositeTaskBuilder.ts so closeEmitter.fire is
invoked exactly once by adding a single shared "closed" flag (or using an
atomic/once guard) checked/updated before firing; update the open() finalization
path and any child PTY exit/CTRL+C handlers (the places that currently call
closeEmitter.fire with exit code 130 and the final success close) to consult
this guard and only fire when it transitions from false to true, and ensure any
related disposables/listeners clear or ignore further close attempts.

Comment on lines +190 to +207
const execution = new vscode.CustomExecution(async () => {
const closeEmitter = new vscode.EventEmitter<number>();

const pty: vscode.Pseudoterminal = {
onDidWrite: new vscode.EventEmitter<string>().event,
onDidClose: closeEmitter.event,

open: () => {
execs.forEach((e, index) => {
const childTask = this.buildExecTask(e, index === 0);
vscode.tasks.executeTask(childTask);
});

closeEmitter.fire(0);
},

close: () => closeEmitter.fire(0),
handleInput: () => {},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In the VS Code Tasks API, what is the recommended way for a CustomExecutiontask to wait for and terminate child tasks started viavscode.tasks.executeTask?

💡 Result:

The recommended way for a CustomExecution task to wait for and terminate child tasks started via vscode.tasks.executeTask is to use the global task events and taskExecutions list within the Pseudoterminal implementation. To wait for child tasks: - Call vscode.tasks.executeTask(task) to get the TaskExecution. - Use vscode.tasks.onDidEndTask or onDidEndTaskProcess with a Promise and disposable listener to wait until the specific child task ends, matching by task properties like source, name, definition, or group (noting historical bugs with direct === comparison). Example: const childExecution = await vscode.tasks.executeTask(childTask); return new Promise((resolve, reject) => { const disposable = vscode.tasks.onDidEndTask(e => { if (matchesTask(e.execution.task, childTask)) { // implement matcher disposable.dispose; if (e.execution.exitCode === 0) resolve; else reject; } }); }); To terminate child tasks: - Access vscode.tasks.taskExecutions (array of active TaskExecution). - Iterate and call terminate on matching TaskExecution instances. - Handle in Pseudoterminal.close method for task cancellation. Example: close { for (const exec of vscode.tasks.taskExecutions) { if (matchesTask(exec.task, expectedChildTask)) { exec.terminate; } } this.closeEmitter.fire(0); // or handle appropriately } This leverages the Tasks API events and TaskExecution.terminate method, as shown in StackOverflow examples and official API docs. CustomExecution manages its own logic via Pseudoterminal, so track child executions explicitly if needed. No official docs show executeTask directly in samples, but API supports it fully.

Citations:


Wait for child task completion before closing the composite.

vscode.tasks.executeTask() returns a TaskExecution handle required to track and terminate the child task. The current code discards this handle and fires close(0) immediately after launching children, so the composite reports success before any child finishes. This also breaks parent-side cancellation—the launched tasks cannot be terminated.

Capture the returned TaskExecution from each executeTask() call, listen for task completion using vscode.tasks.onDidEndTask or onDidEndTaskProcess, and close the parent PTY only after all children finish. (VS Code Task Provider API, TaskExecution reference)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/extensions/che-commands/src/compositeTaskBuilder.ts` around lines 190 -
207, The composite PTY currently fires closeEmitter.fire(0) immediately after
launching children; change it to capture the TaskExecution returned by
vscode.tasks.executeTask(childTask) inside the open() of the CustomExecution
(where execs.forEach and buildExecTask(...) are invoked), track those executions
and wait for their termination via vscode.tasks.onDidEndTaskProcess or
vscode.tasks.onDidEndTask, and only call closeEmitter.fire(0) when all tracked
child TaskExecution instances have completed; also wire parent close() to
cancel/terminate any still-running TaskExecution handles to support proper
cancellation and cleanup.

@github-actions
Copy link

@msivasubramaniaan
Copy link
Collaborator Author

@RomanNikitenko
I have addressed the review comments which you provide, Here are the references of the fixes:

image image

Copy link
Collaborator

@RomanNikitenko RomanNikitenko left a comment

Choose a reason for hiding this comment

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

@msivasubramaniaan
I tested commands with env variables, for example go stack uses this approach: https://github.com/devfile/registry/blob/7c358cd8ef31c59111fac73808eaab7b37afd19c/stacks/go/2.6.0/devfile.yaml#L66-L73

It works for regular commands,
but
env variables are not resolved for composite commands

resolving_env_var_bug.mov

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.

3 participants