Skip to content

[forge] Keep working on the framework#18

Merged
kpouget merged 19 commits intoopenshift-psap:mainfrom
kpouget:launcher
Apr 8, 2026
Merged

[forge] Keep working on the framework#18
kpouget merged 19 commits intoopenshift-psap:mainfrom
kpouget:launcher

Conversation

@kpouget
Copy link
Copy Markdown
Contributor

@kpouget kpouget commented Apr 8, 2026

Summary by CodeRabbit

  • New Features

    • Vault-based secrets management with validation and project integration
    • Template rendering for task outputs and manifest generation
    • CLI/tooling now accepts job metadata (owner/display_name/pipeline) and explicit env passthrough
  • Bug Fixes

    • Cleaner CI notification formatting and safer CI entrypoints to reduce crashes
  • Documentation

    • Added comprehensive vault documentation with examples and remediation steps
  • Configuration

    • Kubeconfig and orchestration config updated to source secrets from vaults

@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 8, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ashishkamra for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 8, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 8, 2026

Warning

Rate limit exceeded

@kpouget has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 4 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 18 minutes and 4 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a71a791-6bb3-4773-95d2-397c76c35ab6

📥 Commits

Reviewing files that changed from the base of the PR and between 2e47b5b and 2b10456.

📒 Files selected for processing (5)
  • projects/fournos_launcher/orchestration/config.yaml
  • projects/fournos_launcher/orchestration/submit.py
  • projects/fournos_launcher/orchestration/test_llmd.py
  • projects/fournos_launcher/toolbox/submit_and_wait/main.py
  • projects/fournos_launcher/toolbox/submit_and_wait/templates/job.yaml.j2
📝 Walkthrough

Walkthrough

Adds a YAML-driven VaultManager for secrets, integrates vaults into launcher/config/CI, introduces task ReadOnlyArgs/TaskContext and Jinja2 template rendering, refactors toolbox/tooling to use shared task context, and applies logging/notification and CLI safety wrapper changes. Documentation added; one project vault removed.

Changes

Cohort / File(s) Summary
Vault system & docs
projects/core/library/vault.py, docs/orchestration/vaults.md
New VaultManager loads FORGE_HOME/vaults/*.yaml, parses vault/content defs, resolves secret file paths from env vars, and provides validation/project helpers; docs describing format and validation added.
Project vaults config
projects/llm_d/orchestration/vaults.yaml
Removed forge-llm-d vault YAML for that project.
Fournos launcher & config
projects/fournos_launcher/orchestration/submit.py, projects/fournos_launcher/orchestration/config.yaml, projects/fournos_launcher/orchestration/ci.py
Launcher now filters used overrides, inits vaults, uses vault-derived kubeconfig via prepare_env(), merges overrides/extra_overrides, passes job metadata/env to submit; main wrapped with @safe_ci_function; config.yaml references psap-forge-ci.
Submit-and-wait / templates
projects/fournos_launcher/toolbox/submit_and_wait/main.py, projects/fournos_launcher/toolbox/submit_and_wait/templates/job.yaml.j2
run gains owner/display_name/pipeline_name/env; tasks refactored to use shared ctx; manifests rendered from Jinja2 template; state moved from argsctx.
Toolbox & capture tools
projects/llm_d/toolbox/capture_isvc_state/main.py
CLI entrypoint converted to toolbox main; run signature made keyword-namespace-only; tasks accept context and use shared context state.
DSL: context & templates
projects/core/dsl/context.py, projects/core/dsl/template.py, projects/core/dsl/__init__.py
Added ReadOnlyArgs and TaskContext, create_task_parameters; new Jinja2 template rendering utilities and helpers; exported context and template in DSL package.
DSL: runtime, task, toolbox, shell, runner
projects/core/dsl/runtime.py, projects/core/dsl/task.py, projects/core/dsl/toolbox.py, projects/core/dsl/shell.py, projects/core/dsl/runner.py
Threaded shared_context through task execution and persisted task_context; TaskExecutionError stores task_context; refined decorator detection; CLI/toolbox API simplified; logging/output formatting standardized.
CI exception handling & notifications
projects/core/library/ci.py, projects/core/notifications/send.py, projects/llm_d/orchestration/ci.py, projects/skeleton/orchestration/ci.py
Added safe_ci_function decorator (wraps CI mains, exits after handling exceptions); adjusted stacktrace delimiter behavior and removed emphasis from failure label in notifications; applied decorator to project mains.
Config helper
projects/core/library/config.py
Added Config.filter_out_used_overrides() to prune overrides already present in current config.
Tests / cleanup
projects/fournos_launcher/orchestration/test_llmd.py, projects/llm_d/orchestration/test_llmd.py
Removed launcher test module; updated llm_d test to pass namespace as keyword argument.
Minor formatting & messaging
projects/core/dsl/runner.py, projects/core/dsl/shell.py, projects/core/notifications/send.py
Logger/message formatting tweaks and small notification text adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI / main()
    participant Launcher as Fournos Launcher
    participant Config as Config System
    participant Vault as VaultManager
    participant Env as Environment
    participant Job as Fournos Job

    CLI->>Launcher: init()
    Launcher->>Config: read project config (includes `vaults`)
    Config-->>Launcher: config + vaults list
    Launcher->>Vault: init(vaults=[...])
    Vault->>Vault: load & validate vault YAMLs from FORGE_HOME/vaults
    Vault-->>Launcher: VaultManager ready
    Launcher->>Vault: get_vault_content_path(vault, key)
    Vault-->>Launcher: /resolved/path/to/secret
    Launcher->>Env: set env var (e.g. KUBECONFIG) from path
    Launcher->>Job: submit_job(owner, display_name, pipeline_name, env)
    Job->>Env: read KUBECONFIG + provided env
    Env-->>Job: kubeconfig path + env vars
    Job-->>CLI: submission result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped in with YAML, neat and spry,
Secrets tucked in folders where foxes can't pry.
Tasks get readonly keys, contexts share the tune,
CI wears a safety cap beneath the moon.
Hooray — templates, vaults, and a launcher boon!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.40% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using non-descriptive language ('Keep working on the framework') that fails to convey the substantial scope and nature of the changes across multiple modules and features. Consider using a more descriptive title that highlights the main changes, such as 'Add vault-based secrets management system and refactor task/toolbox framework' or break into multiple focused PRs with specific titles.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@openshift-ci openshift-ci bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 8, 2026
@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

• Failure indicator: Empty.
Execution logs

@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Failure indicator:

## /logs/artifacts/FAILURE 
--- 📍 STACKTRACE

[...]

Execution logs

@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Failure indicator:

## /logs/artifacts/FAILURE 
--- 📍 STACKTRACE

   Traceback (most recent call last):
     File "/app/forge/projects/core/library/ci.py", line 96, in wrapper
       exit_code = command_func(*args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     File "/app/forge/projects/fournos_launcher/orchestration/ci.py", line 29, in main
       submit_mod.init()
     File "/app/forge/projects/fournos_launcher/orchestration/submit.py", line 17, in init
       vault.init(config.project.get_config("vaults"))
     File "/app/forge/projects/core/library/vault.py", line 373, in init
       _filter_and_validate_vaults(_vault_manager, vaults)
     File "/app/forge/projects/core/library/vault.py", line 346, in _filter_and_validate_vaults
       raise RuntimeError("One or more vaults failed validation")
   RuntimeError: One or more vaults failed validation

[...]

Execution logs

@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Execution logs

@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Failure indicator:

## /logs/artifacts/FAILURE 
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
~~ projects/fournos_launcher/toolbox/submit_and_wait/main.py:128
~~ TASK: submit_fournos_job: Submit the FOURNOS job
~~ ARTIFACT_DIR: /logs/artifacts/001__submit_and_wait
~~ LOG_FILE: /logs/artifacts/001__submit_and_wait/task.log
~~ ARGS:
~~     cluster_name: psap
~~     project: llm_d
~~     args: &id001
~~     - hello
~~     - world
~~     variables_overrides: &id002
~~       something.is.wrong: 'false'
~~     job_name: ''
~~     namespace: psap-automation
~~     artifact_dir: /logs/artifacts/001__submit_and_wait
~~     args_list: *id001
~~     variables_dict: *id002
~~     final_job_name: forge-llm_d-20260408-122424
~~     manifest_file: /logs/artifacts/001__submit_and_wait/src/forge-llm_d-20260408-122424-manifest.yaml
~~
~~ EXCEPTION: CalledProcessError
~~     Command 'oc apply -f /logs/artifacts/001__submit_and_wait/src/forge-llm_d-20260408-122424-manifest.yaml' returned non-zero exit status 1.
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


[...]

Execution logs

Copy link
Copy Markdown

@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: 3

🧹 Nitpick comments (9)
projects/core/dsl/task.py (1)

132-144: Annotation check conditions are likely ineffective.

The check params[0].annotation in (inspect.Parameter.empty, 'func', callable) at line 138 has issues:

  • 'func' is a string literal, but annotations are typically types or inspect.Parameter.empty
  • callable is the built-in function, not a type annotation like Callable

In practice this works because most decorator parameters are unannotated (inspect.Parameter.empty), but the extra conditions are misleading. Consider simplifying or using proper type checks.

♻️ Suggested simplification
         # Check if this is a simple decorator that takes a single function argument
-        if (len(params) == 1 and
-            params[0].annotation in (inspect.Parameter.empty, 'func', callable) and
-            len(args) == 1 and len(kwargs) == 0 and
-            callable(args[0]) and hasattr(args[0], '__name__')):
+        if (len(params) == 1 and
+            params[0].annotation is inspect.Parameter.empty and
+            len(args) == 1 and len(kwargs) == 0 and
+            callable(args[0]) and hasattr(args[0], '__name__')):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/core/dsl/task.py` around lines 132 - 144, The annotation check in
the decorator detection is wrong/misleading: replace the tuple check on
params[0].annotation (params[0].annotation in (inspect.Parameter.empty, 'func',
callable)) with a simple proper test that the parameter is unannotated
(params[0].annotation is inspect.Parameter.empty) or otherwise remove
annotation-based checks entirely; keep the surrounding logic (len(params) == 1,
len(args) == 1, len(kwargs) == 0, callable(args[0]) and hasattr(args[0],
'__name__')) and then call _ensure_is_task(func, decorator_func.__name__) as
before so decorator_func, params, args, kwargs and _ensure_is_task remain the
referenced symbols to locate the change.
docs/orchestration/vaults.md (3)

83-91: Add language specifiers to code blocks for consistency.

The code blocks showing file path resolution patterns are missing language specifiers. While these are simple text examples, adding a language identifier improves markdown linting compliance and rendering consistency.

📝 Proposed fix
 ### File Path Resolution

 Vault content paths are resolved as:
-```
+```text
 {env_var_value}/{filename}

For example, with PSAP_FORGE_FOURNOS_CI_SECRET_PATH=/secrets/ci and content named kubeconfig:
- +text
/secrets/ci/kubeconfig

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

In `@docs/orchestration/vaults.md` around lines 83 - 91, The markdown code blocks
in docs/orchestration/vaults.md that show the vault path pattern and example
should include a language specifier to satisfy linting; update the two
triple-backtick blocks surrounding `{env_var_value}/{filename}` and
`/secrets/ci/kubeconfig` to use ```text (i.e., add "text" after the opening
backticks) so both fenced code blocks explicitly declare the language.

154-165: Add language specifier to directory structure example.

📝 Proposed fix
 ### Directory Structure

 Vault directories should contain only the files defined in the vault specification:

-```
+```text
 /path/to/ci/secrets/
 ├── kubeconfig          # Defined in vault spec
 ├── server_url          # Defined in vault spec
 └── api_token           # Defined in vault spec
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/orchestration/vaults.md around lines 154 - 165, The code block showing
the vault directory structure in docs/orchestration/vaults.md lacks a language
specifier; update the fenced code block markers around the directory example
(the triple backticks surrounding the tree under "Directory Structure") to
include "text" (i.e., change totext) so the block is rendered with the
correct language hint.


</details>

---

`265-291`: **Add language specifiers to error message examples.**

The error message examples in the Error Handling section are missing language specifiers.

<details>
<summary>📝 Proposed fix</summary>

```diff
 **Missing environment variable:**
-```
+```text
 Vault 'my-vault' requires environment variable MY_VAULT_SECRET_PATH to be set
 ```
 Solution: Export the required environment variable

 **Missing description:**
-```
+```text
 Vault 'my-vault' content 'api_key' is missing description
 ```
 Solution: Add description to vault definition

 **Extra files:**
-```
+```text
 Vault 'my-vault' contains extra file 'backup.txt' at '/path/vault/backup.txt' not defined in specification
 ```
 Solution: Either remove the file or add it to the vault definition

 **Missing content file:**
-```
+```text
 Vault 'my-vault' missing content 'api_key' at: /path/vault/api_key
 ```
 Solution: Create the missing secret file
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/orchestration/vaults.md around lines 265 - 291, Update the Error
Handling examples in docs/orchestration/vaults.md to add language specifiers
(use ```text) for each code block containing the example error messages (the
four blocks under "Missing environment variable", "Missing description", "Extra
files", and "Missing content file") so the fenced code blocks explicitly declare
the language; ensure you replace the plain triple-backtick blocks with
triple-backticks followed by text for the examples in that section.


</details>

</blockquote></details>
<details>
<summary>projects/core/library/vault.py (5)</summary><blockquote>

`360-360`: **Use explicit `Optional[List[str]]` type hint.**

Same as line 68 - the default `None` requires an explicit `Optional` annotation per PEP 484.

<details>
<summary>📝 Proposed fix</summary>

```diff
-def init(vaults: List[str] = None):
+def init(vaults: Optional[List[str]] = None):
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/vault.py` at line 360, The function signature for init
currently uses a default None without an explicit Optional type; update its
annotation to use Optional[List[str]] for the vaults parameter (i.e., change def
init(vaults: List[str] = None): to use Optional[List[str]]), and ensure
typing.Optional is imported (or already used elsewhere, e.g., as on line 68) so
the annotation is consistent with PEP 484 across the module.
```

</details>

---

`113-118`: **Add explicit check for required `env_key` field.**

If `env_key` is missing from the YAML file, this will raise a `KeyError` with an unclear message. Consider validating required fields explicitly for better error reporting.

<details>
<summary>📝 Proposed fix</summary>

```diff
+        if 'env_key' not in data:
+            raise ValueError(f"Vault definition missing required 'env_key' field: {vault_file}")
+
         vault_def = VaultDefinition(
             name=vault_name,
             env_key=data['env_key'],
             description=data.get('description', ''),
             content=content
         )
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/vault.py` around lines 113 - 118, Before constructing
VaultDefinition, explicitly validate that the required key 'env_key' exists and
is non-empty in the parsed YAML dict (data); if missing or empty, raise a clear
ValueError (or custom exception) with a descriptive message referencing the
vault name or source file so callers know which vault is malformed. Update the
block around vault_def creation (the code that builds VaultDefinition and
assigns to vault_def) to perform this check on data['env_key'] (or
data.get('env_key')) and only proceed to call VaultDefinition(...) when the
check passes.
```

</details>

---

`18-19`: **Remove unused `TYPE_CHECKING` block.**

The `TYPE_CHECKING` import is used but the conditional block only contains `pass`, making it dead code. Either remove it entirely or add the forward reference imports that justify its existence.

<details>
<summary>🧹 Proposed fix</summary>

```diff
-from typing import Dict, List, Optional, Set, TYPE_CHECKING
+from typing import Dict, List, Optional, Set
 from dataclasses import dataclass

-if TYPE_CHECKING:
-    pass
-
 import projects.core.library.env as env
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/vault.py` around lines 18 - 19, The conditional
TYPE_CHECKING block in vault.py is dead code (it only contains "pass"); remove
the import of TYPE_CHECKING and the empty "if TYPE_CHECKING: pass" block, or
alternatively populate that block with the intended forward-reference imports
used only for typing; specifically update references to the TYPE_CHECKING symbol
and the conditional block around it so there are no unused imports or empty
conditionals (e.g., remove TYPE_CHECKING import and the "if TYPE_CHECKING" block
unless you add forward-only type imports for classes used elsewhere in this
module).
```

</details>

---

`68-71`: **Use explicit `Optional[Path]` type hint.**

PEP 484 prohibits implicit `Optional`. The default `None` requires an explicit `Optional` annotation.

<details>
<summary>📝 Proposed fix</summary>

```diff
-    def __init__(self, vault_definitions_dir: Path = None):
+    def __init__(self, vault_definitions_dir: Optional[Path] = None):
         self.vault_definitions_dir = vault_definitions_dir or env.FORGE_HOME / "vaults"
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/vault.py` around lines 68 - 71, The __init__ signature
currently uses an implicit None default for vault_definitions_dir without an
Optional annotation; update the parameter type to Optional[Path] (i.e., change
def __init__(self, vault_definitions_dir: Optional[Path] = None)) and add the
corresponding import for Optional from typing at the top of the module, keeping
the rest of the logic (assigning self.vault_definitions_dir and _vault_cache and
calling _load_vault_definitions) unchanged.
```

</details>

---

`79-86`: **Consider narrowing the exception type or re-raising after logging.**

Catching a broad `Exception` during vault loading is reasonable for robustness (one malformed file shouldn't prevent loading others), but this can mask unexpected errors (e.g., permission issues, disk I/O errors). Consider catching more specific exceptions like `yaml.YAMLError` and `OSError`, or at minimum logging the full traceback for debugging.

<details>
<summary>📝 Proposed fix with traceback</summary>

```diff
             try:
                 vault_def = self._load_vault_definition(vault_file)
                 self._vault_cache[vault_def.name] = vault_def
                 logger.debug(f"Loaded vault definition: {vault_def.name}")
-            except Exception as e:
-                logger.error(f"Failed to load vault definition from {vault_file}: {e}")
+            except (yaml.YAMLError, OSError, KeyError) as e:
+                logger.error(f"Failed to load vault definition from {vault_file}: {e}", exc_info=True)
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/vault.py` around lines 79 - 86, The loop that loads
vault defs currently catches all Exception in projects/core/library/vault.py
(around the self.vault_definitions_dir.glob("*.yaml") loop calling
self._load_vault_definition) which can mask real errors; change it to catch
specific expected errors (e.g., yaml.YAMLError and OSError) and use
logger.exception(...) to record the full traceback when those occur, and for any
other unexpected exceptions re-raise after logging so they aren’t silently
swallowed (keep storing results in self._vault_cache as before for successful
loads).
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Inline comments:
In @projects/core/library/config.py:

  • Around line 289-303: In filter_out_used_overrides, get_config("overrides", {})
    can return None when overrides is explicitly null, causing overrides.items() to
    fail; update the function to treat a None (or non-dict) overrides as an empty
    dict before iterating (e.g., if not isinstance(overrides, dict): overrides =
    {}), then proceed with the existing loop and call to set_config; reference
    filter_out_used_overrides, get_config, has_config, and set_config when making
    the change.

In @projects/core/library/vault.py:

  • Around line 382-397: The convenience functions get_vault_manager,
    validate_project_vaults, and get_vault_content_path currently assume
    _vault_manager is initialized and will raise AttributeError if init() wasn't
    called; update get_vault_manager to check if _vault_manager is None and raise a
    clear RuntimeError (or ValueError) like "Vault manager not initialized; call
    init() first" (or alternatively change its return type to Optional[VaultManager]
    and propagate Optional typing), and update validate_project_vaults and
    get_vault_content_path to either call the guarded get_vault_manager (so they
    inherit the clear error) or explicitly guard for None before calling methods on
    the manager.

In @projects/fournos_launcher/orchestration/submit.py:

  • Around line 16-17: config.project.get_config("vaults") can raise KeyError when
    the key is absent; change the retrieval to provide a safe default before calling
    vault.init. Replace the direct call with a guarded fetch (e.g., call get_config
    with a default like None or wrap in try/except KeyError) so that vault.init
    receives None/[] when "vaults" is missing; update the code around
    config.project.get_config("vaults") and the subsequent vault.init(...) call to
    use that default value.

Nitpick comments:
In @docs/orchestration/vaults.md:

  • Around line 83-91: The markdown code blocks in docs/orchestration/vaults.md
    that show the vault path pattern and example should include a language specifier
    to satisfy linting; update the two triple-backtick blocks surrounding
    {env_var_value}/{filename} and /secrets/ci/kubeconfig to use ```text (i.e.,
    add "text" after the opening backticks) so both fenced code blocks explicitly
    declare the language.
  • Around line 154-165: The code block showing the vault directory structure in
    docs/orchestration/vaults.md lacks a language specifier; update the fenced code
    block markers around the directory example (the triple backticks surrounding the
    tree under "Directory Structure") to include "text" (i.e., change ``` to
- Around line 265-291: Update the Error Handling examples in
docs/orchestration/vaults.md to add language specifiers (use ```text) for each
code block containing the example error messages (the four blocks under "Missing
environment variable", "Missing description", "Extra files", and "Missing
content file") so the fenced code blocks explicitly declare the language; ensure
you replace the plain triple-backtick blocks with triple-backticks followed by
text for the examples in that section.

In `@projects/core/dsl/task.py`:
- Around line 132-144: The annotation check in the decorator detection is
wrong/misleading: replace the tuple check on params[0].annotation
(params[0].annotation in (inspect.Parameter.empty, 'func', callable)) with a
simple proper test that the parameter is unannotated (params[0].annotation is
inspect.Parameter.empty) or otherwise remove annotation-based checks entirely;
keep the surrounding logic (len(params) == 1, len(args) == 1, len(kwargs) == 0,
callable(args[0]) and hasattr(args[0], '__name__')) and then call
_ensure_is_task(func, decorator_func.__name__) as before so decorator_func,
params, args, kwargs and _ensure_is_task remain the referenced symbols to locate
the change.

In `@projects/core/library/vault.py`:
- Line 360: The function signature for init currently uses a default None
without an explicit Optional type; update its annotation to use
Optional[List[str]] for the vaults parameter (i.e., change def init(vaults:
List[str] = None): to use Optional[List[str]]), and ensure typing.Optional is
imported (or already used elsewhere, e.g., as on line 68) so the annotation is
consistent with PEP 484 across the module.
- Around line 113-118: Before constructing VaultDefinition, explicitly validate
that the required key 'env_key' exists and is non-empty in the parsed YAML dict
(data); if missing or empty, raise a clear ValueError (or custom exception) with
a descriptive message referencing the vault name or source file so callers know
which vault is malformed. Update the block around vault_def creation (the code
that builds VaultDefinition and assigns to vault_def) to perform this check on
data['env_key'] (or data.get('env_key')) and only proceed to call
VaultDefinition(...) when the check passes.
- Around line 18-19: The conditional TYPE_CHECKING block in vault.py is dead
code (it only contains "pass"); remove the import of TYPE_CHECKING and the empty
"if TYPE_CHECKING: pass" block, or alternatively populate that block with the
intended forward-reference imports used only for typing; specifically update
references to the TYPE_CHECKING symbol and the conditional block around it so
there are no unused imports or empty conditionals (e.g., remove TYPE_CHECKING
import and the "if TYPE_CHECKING" block unless you add forward-only type imports
for classes used elsewhere in this module).
- Around line 68-71: The __init__ signature currently uses an implicit None
default for vault_definitions_dir without an Optional annotation; update the
parameter type to Optional[Path] (i.e., change def __init__(self,
vault_definitions_dir: Optional[Path] = None)) and add the corresponding import
for Optional from typing at the top of the module, keeping the rest of the logic
(assigning self.vault_definitions_dir and _vault_cache and calling
_load_vault_definitions) unchanged.
- Around line 79-86: The loop that loads vault defs currently catches all
Exception in projects/core/library/vault.py (around the
self.vault_definitions_dir.glob("*.yaml") loop calling
self._load_vault_definition) which can mask real errors; change it to catch
specific expected errors (e.g., yaml.YAMLError and OSError) and use
logger.exception(...) to record the full traceback when those occur, and for any
other unexpected exceptions re-raise after logging so they aren’t silently
swallowed (keep storing results in self._vault_cache as before for successful
loads).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65147250-82cc-4618-a1a4-315e48424293

📥 Commits

Reviewing files that changed from the base of the PR and between 53a66cc and 7cf6b86.

📒 Files selected for processing (12)
  • docs/orchestration/vaults.md
  • projects/core/dsl/task.py
  • projects/core/library/ci.py
  • projects/core/library/config.py
  • projects/core/library/vault.py
  • projects/core/notifications/send.py
  • projects/fournos_launcher/orchestration/ci.py
  • projects/fournos_launcher/orchestration/config.yaml
  • projects/fournos_launcher/orchestration/submit.py
  • projects/llm_d/orchestration/ci.py
  • projects/llm_d/orchestration/vaults.yaml
  • projects/skeleton/orchestration/ci.py
💤 Files with no reviewable changes (1)
  • projects/llm_d/orchestration/vaults.yaml

Comment thread projects/core/library/config.py
Comment thread projects/core/library/vault.py
Comment thread projects/fournos_launcher/orchestration/submit.py
@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Failure indicator:

## /logs/artifacts/FAILURE 
--- 📍 STACKTRACE

   Traceback (most recent call last):
     File "/app/forge/projects/core/library/config.py", line 185, in get_config
       value = jsonpath_ng.parse(jsonpath).find(self.config)[0].value
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
   IndexError: list index out of range
   
   During handling of the above exception, another exception occurred:
   
   Traceback (most recent call last):
     File "/app/forge/projects/core/library/ci.py", line 96, in wrapper
       exit_code = command_func(*args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     File "/app/forge/projects/fournos_launcher/orchestration/ci.py", line 38, in submit
       return submit_mod.submit_job()
              ^^^^^^^^^^^^^^^^^^^^^^^
     File "/app/forge/projects/fournos_launcher/orchestration/submit.py", line 38, in submit_job
       env_pass_lists = config.project.get_config("fournos.env", print=False)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     File "/app/forge/projects/core/library/config.py", line 193, in get_config
       raise KeyError(f"Key '{jsonpath}' not found in {self.config_path}")
   KeyError: "Key 'fournos.env' not found in /logs/artifacts/config.yaml"

[...]

Execution logs

@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Failure indicator:

## /logs/artifacts/FAILURE 
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
~~ projects/fournos_launcher/toolbox/submit_and_wait/main.py:114
~~ TASK: create_job_manifest: Create FOURNOS job manifest
~~ ARTIFACT_DIR: /logs/artifacts/001__submit_and_wait
~~ LOG_FILE: /logs/artifacts/001__submit_and_wait/task.log
~~ ARGS:
~~     cluster_name: psap
~~     project: llm_d
~~     args:
~~     - hello
~~     - world
~~     variables_overrides:
~~       something.is.wrong: 'false'
~~     job_name: ''
~~     namespace: psap-automation
~~     owner: fournos-launcher
~~     display_name: Forge Job
~~     pipeline_name: forge-test-only
~~     env:
~~       JOB_TYPE: presubmit
~~       ENTRYPOINT_OPTIONS: '{"timeout":82800000000000,"grace_period":15000000000,"artifact_dir":"/logs/artifacts","args":["/bin/bash","-c","#!/bin/bash\nset
~~         -eu\nexport PSAP_FORGE_FOURNOS_CI_SECRET_PATH=/var/run/psap-forge-ci\nexport PSAP_FORGE_NOTIFICATIONS_SECRET_PATH=/var/run/psap-forge-notifications\n\ngit
~~         -C $HOME fetch --quiet origin $PULL_PULL_SHA\ngit -C $HOME reset --hard FETCH_HEAD\n\nexec
~~         ./bin/run_ci fournos ci submit\n"],"container_name":"test","process_log":"/logs/process-log.txt","marker_file":"/logs/marker-file.txt","metadata_file":"/logs/artifacts/metadata.json"}'
~~       JOB_NAME: pull-ci-openshift-psap-forge-main-fournos
~~       JOB_SPEC: '{"type":"presubmit","job":"pull-ci-openshift-psap-forge-main-fournos","buildid":"2041882462585360384","prowjobid":"87e10460-3685-482b-9a26-3c763fbccc46","refs":{"org":"openshift-psap","repo":"forge","repo_link":"https://github.com/openshift-psap/forge","base_ref":"main","base_sha":"53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9","base_link":"https://github.com/openshift-psap/forge/commit/53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9","pulls":[{"number":18,"author":"kpouget","sha":"3421b7232cc2202b1011af1833a72166b377e83b","title":"[forge]
~~         Keep working on the framework","head_ref":"launcher","link":"https://github.com/openshift-psap/forge/pull/18","commit_link":"https://github.com/openshift-psap/forge/pull/18/commits/3421b7232cc2202b1011af1833a72166b377e83b","author_link":"https://github.com/kpouget"}]},"decoration_config":{"timeout":"23h0m0s","grace_period":"15s","utility_images":{"clonerefs":"us-docker.pkg.dev/k8s-infra-prow/images/clonerefs:v20260328-dc2bbc9fc","initupload":"us-docker.pkg.dev/k8s-infra-prow/images/initupload:v20260328-dc2bbc9fc","entrypoint":"us-docker.pkg.dev/k8s-infra-prow/images/entrypoint:v20260328-dc2bbc9fc","sidecar":"us-docker.pkg.dev/k8s-infra-prow/images/sidecar:v20260328-dc2bbc9fc"},"resources":{"clonerefs":{"limits":{"memory":"3Gi"},"requests":{"cpu":"100m","memory":"500Mi"}},"initupload":{"limits":{"memory":"200Mi"},"requests":{"cpu":"100m","memory":"50Mi"}},"place_entrypoint":{"limits":{"memory":"100Mi"},"requests":{"cpu":"100m","memory":"25Mi"}},"sidecar":{"limits":{"memory":"2Gi"},"requests":{"cpu":"100m","memory":"250Mi"}}},"gcs_configuration":{"bucket":"test-platform-results","path_strategy":"single","default_org":"openshift","default_repo":"origin","mediaTypes":{"log":"text/plain"},"compress_file_types":["txt","log","json","tar","html","yaml"]},"gcs_credentials_secret":"gce-sa-credentials-gcs-publisher","skip_cloning":true,"censor_secrets":true,"censoring_options":{"minimum_secret_length":6}}}'
~~       OPENSHIFT_CI: 'true'
~~       JOB_NAME_SAFE: fournos
~~       BUILD_ID: '2041882462585360384'
~~       PULL_PULL_SHA: 3421b7232cc2202b1011af1833a72166b377e83b
~~       PULL_NUMBER: '18'
~~       PULL_BASE_REF: main
~~       REPO_NAME: forge
~~       REPO_OWNER: openshift-psap
~~       PULL_BASE_SHA: 53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9
~~       PULL_TITLE: '[forge] Keep working on the framework'
~~       PULL_REFS: main:53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9,18:3421b7232cc2202b1011af1833a72166b377e83b
~~       PULL_HEAD_REF: launcher
~~     artifact_dir: /logs/artifacts/001__submit_and_wait
~~ CONTEXT:
~~     final_job_name: forge-llm_d-20260408-141528
~~     manifest_file: /logs/artifacts/001__submit_and_wait/src/forge-llm_d-20260408-141528-manifest.yaml
~~
~~ EXCEPTION: FileNotFoundError
~~     Templates directory not found: /app/forge/projects/fournos_launcher/toolbox/submit_and_wait/templates
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


[...]

Execution logs

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
projects/core/dsl/runner.py (1)

38-64: ⚠️ Potential issue | 🟠 Major

Keep the entire command trace on a single output path.

Line 38 uses logger.info(), but lines 57-64 still use print() directly. Unlike projects/core/dsl/shell.py, this module doesn't call logging.basicConfig(), so the command banner may not appear while the output lines still print—splitting the trace across output channels. Consolidate to use only logger.info() throughout, as shown in shell.py.

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

In `@projects/core/dsl/runner.py` around lines 38 - 64, The command trace is split
between logger.info and print calls in projects/core/dsl/runner.py (around the
subprocess.run / CommandResult handling); replace the direct print(...) calls
that output stdout, stderr and exit code with logger.info(...) calls (preserving
the same formatted strings and .strip() usage) and also replace the trailing
print() with logger.info("") so the entire banner and command output go through
the same logging path (use the existing logger variable and keep constructing
CommandResult as-is).
♻️ Duplicate comments (1)
projects/core/library/vault.py (1)

382-397: ⚠️ Potential issue | 🟡 Minor

Guard against uninitialized vault manager in convenience functions.

If init() hasn't been called, get_vault_manager() returns None, and calling methods like validate_project_vaults() or get_vault_content_path() will raise an AttributeError.

🛡️ Proposed fix
 def get_vault_manager() -> VaultManager:
     """Get the global vault manager instance"""
+    if _vault_manager is None:
+        raise RuntimeError("VaultManager not initialized. Call vault.init() first.")
     return _vault_manager

Alternatively, update the return type to Optional[VaultManager] if callers are expected to handle None.

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

In `@projects/core/library/vault.py` around lines 382 - 397, The convenience
functions validate_project_vaults and get_vault_content_path currently call
get_vault_manager() which can return None if init() wasn't called; update both
functions to guard against a None vault manager by retrieving vm =
get_vault_manager(), checking if vm is None and raising a clear runtime error
(e.g., "Vault manager not initialized; call init() first") instead of calling
methods on None, then delegate to vm.validate_project_vaults(...) and
vm.get_vault_content_path(...) when present; ensure the error message names the
function (validate_project_vaults/get_vault_content_path) and suggests calling
init() to help debugging.
🧹 Nitpick comments (7)
projects/core/library/vault.py (1)

68-68: Use explicit Optional for type hints.

PEP 484 prohibits implicit Optional. The parameter vault_definitions_dir: Path = None should use explicit optional typing.

♻️ Proposed fix
-    def __init__(self, vault_definitions_dir: Path = None):
+    def __init__(self, vault_definitions_dir: Optional[Path] = None):

Similarly for line 360:

-def init(vaults: List[str] = None):
+def init(vaults: Optional[List[str]] = None):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/vault.py` at line 68, The type hints use implicit
Optional (e.g., vault_definitions_dir: Path = None in Vault.__init__) which
violates PEP 484; update the signatures to use explicit Optional[Path] and
import Optional from typing (add "from typing import Optional" if missing), and
apply the same change to the other occurrence referenced around line 360
(replace the parameter currently annotated as Path = None with Optional[Path]).
Ensure imports are updated and any type references use Optional[Path]
consistently.
docs/orchestration/vaults.md (3)

158-165: Add language identifier to directory structure code block.

📝 Proposed fix
-```
+```text
 /path/to/ci/secrets/
 ├── kubeconfig          # Defined in vault spec
 ├── server_url          # Defined in vault spec
 └── api_token           # Defined in vault spec
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/orchestration/vaults.md around lines 158 - 165, The fenced code block
showing the directory structure is missing a language identifier; update the
triple-backtick fence around the directory tree in the vaults.md content to
include a language tag (use "text") so the block becomes text ... which
ensures correct rendering and preserves spacing for the directory structure.


</details>

---

`84-91`: **Add language identifiers to fenced code blocks.**

The code blocks showing path resolution examples should have a language identifier (e.g., `text` or `plaintext`) for consistency and to satisfy linters.


<details>
<summary>📝 Proposed fix</summary>

```diff
-```
+```text
 {env_var_value}/{filename}
 ```

 For example, with `PSAP_FORGE_FOURNOS_CI_SECRET_PATH=/secrets/ci` and content named `kubeconfig`:
-```
+```text
 /secrets/ci/kubeconfig
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/orchestration/vaults.md around lines 84 - 91, Add language identifiers
to the two fenced code blocks that show path resolution examples in the
docs/orchestration/vaults.md snippet: change the opening triple-backtick fences
for the examples containing "{env_var_value}/{filename}" and
"/secrets/ci/kubeconfig" to include a language identifier such as "text" (i.e.,


265-291: Add language identifiers to error example code blocks.

The error message examples at lines 270-291 should have language identifiers.

📝 Proposed fix
-```
+```text
 Vault 'my-vault' requires environment variable MY_VAULT_SECRET_PATH to be set

- +text
Vault 'my-vault' content 'api_key' is missing description


-```
+```text
Vault 'my-vault' contains extra file 'backup.txt' at '/path/vault/backup.txt' not defined in specification

- +text
Vault 'my-vault' missing content 'api_key' at: /path/vault/api_key

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

In `@docs/orchestration/vaults.md` around lines 265 - 291, In the "Error Handling"
section (the four error example blocks under the header), add explicit language
identifiers to each fenced code block by changing the opening triple backticks
to ```text for the examples: "Vault 'my-vault' requires environment variable
MY_VAULT_SECRET_PATH to be set", "Vault 'my-vault' content 'api_key' is missing
description", "Vault 'my-vault' contains extra file 'backup.txt' at
'/path/vault/backup.txt' not defined in specification", and "Vault 'my-vault'
missing content 'api_key' at: /path/vault/api_key" so they render with the text
language tag and consistent formatting.
projects/fournos_launcher/toolbox/submit_and_wait/main.py (2)

28-28: Use explicit Optional for type hint.

PEP 484 prohibits implicit Optional. The parameter env: dict = None should use explicit optional typing.

♻️ Proposed fix
-    env: dict = None,
+    env: Optional[dict] = None,

And add Optional to imports:

-from typing import Any
+from typing import Any, Optional
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py` at line 28, The
parameter declaration using a nullable default (env: dict = None) violates PEP
484; change the annotation to use explicit Optional by updating the parameter to
env: Optional[dict] = None and add Optional to your typing imports (e.g.,
include Optional in the existing from typing import ... list or add from typing
import Optional); ensure you update the signature where the env parameter is
defined so tools and linters recognize the optional type.

90-90: Remove unnecessary f-string prefix.

The string f"Inputs validated" has no placeholders and should be a regular string.

♻️ Proposed fix
-    return f"Inputs validated"
+    return "Inputs validated"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py` at line 90,
Replace the unnecessary f-string prefix on the return statement that currently
reads return f"Inputs validated" with a plain string literal (return "Inputs
validated") so no formatting is used; locate the return f"Inputs validated" in
the function that performs input validation and remove the leading f character.
projects/core/dsl/template.py (1)

76-84: Consider removing misleading autoescape setting.

The comment says "Enable auto-escaping for security (though we're generating YAML, not HTML)" but autoescape is configured for ['html', 'xml'], which won't affect YAML output. Since this generates YAML, the autoescape setting is effectively a no-op but the configuration and comment may confuse future maintainers.

♻️ Proposed simplification
     # Set up Jinja2 environment
     env = jinja2.Environment(
         loader=jinja2.FileSystemLoader(templates_dir),
-        # Enable auto-escaping for security (though we're generating YAML, not HTML)
-        autoescape=jinja2.select_autoescape(['html', 'xml']),
+        # No autoescape needed for YAML output
+        autoescape=False,
         # Raise errors for undefined variables instead of silently ignoring
         undefined=jinja2.StrictUndefined,
         # Keep line endings consistent
         keep_trailing_newline=True
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/core/dsl/template.py` around lines 76 - 84, The autoescape parameter
in the jinja2.Environment call is misleading for YAML generation; update the env
construction (the jinja2.Environment call that currently references
templates_dir and autoescape) by removing the autoescape argument and its
misleading comment (or set autoescape=None) so the configuration and comment
accurately reflect that auto-escaping is not used for YAML; keep the other
options (undefined=jinja2.StrictUndefined, keep_trailing_newline=True)
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/core/dsl/context.py`:
- Around line 16-18: ReadOnlyArgs currently stores the original args_dict by
reference (object.__setattr__(self, '_args_dict', args_dict)) and
create_task_parameters() copies shared_context attributes by reference, allowing
in-place mutations like args.env[...] or context.some_list.append(...) to leak
across tasks; fix by performing defensive deep copies of any mutable values when
constructing ReadOnlyArgs (copy.deepcopy on values placed into _args_dict) and
when building task contexts in create_task_parameters(), and likewise replace
the direct setattr(shared_context, ...) assignment in runtime.py with a
defensive copy of the value before assigning so task-local modifications cannot
affect the shared objects.

In `@projects/core/dsl/toolbox.py`:
- Around line 107-109: The code yields serialized task_context without guarding
for None and may leak secrets; update the block that calls
clean_args(e.task_context) and yaml.dump (around the generator yielding "~~
CONTEXT:" in toolbox.py) to first check if e.task_context is None and yield a
clear "no context" line instead, and when task_context is present pass only a
safe, allowlisted subset (or a redacted copy) to clean_args before yaml.dump;
ensure clean_args is only called with a dict and that sensitive keys are
filtered/masked prior to serialization.

In `@projects/fournos_launcher/orchestration/submit.py`:
- Around line 22-28: prepare_env currently blindly sets os.environ["KUBECONFIG"]
= str(kubeconfig_path) which will set the literal "None" if
vault.get_vault_content_path returns None; change prepare_env to check the
result of vault.get_vault_content_path (the kubeconfig_path variable) and if
it's None either raise a clear exception or log an error and exit, otherwise set
os.environ["KUBECONFIG"] to the valid path (converted to string). Reference
prepare_env, vault.get_vault_content_path, and the os.environ["KUBECONFIG"]
assignment when implementing the guard and error handling.

In `@projects/llm_d/toolbox/capture_isvc_state/main.py`:
- Around line 14-18: The change introduced a keyword-only parameter by adding
"*" to the run signature which breaks existing callers that call
capture_isvc_state(_cfg.name, _cfg.ns) positionally; either remove the "*" from
the run(llmisvc_name: str, *, namespace: str = "") signature so namespace
remains positional, or update the callers of capture_isvc_state to pass
namespace as a keyword (capture_isvc_state(_cfg.name, namespace=_cfg.ns));
locate the function named run and the call sites named capture_isvc_state in the
tests and apply one consistent fix across the repo.
- Around line 63-65: The current use of f-string interpolation into
shell.run(...) (e.g., the oc invocations around shell.run calls that build
commands like 'oc get llminferenceservice {args.llmisvc_name} -n
{context.target_namespace} -oyaml') is vulnerable to command injection because
shell=True is used by default in projects/core/dsl/shell.py::run; change each
interpolated call to pass the command as a list (argv form) and set shell=False
and explicit check=False where appropriate (for example the shell.run call that
fetches llminferenceservice and all other similar oc commands around lines 64,
75, 86, 97) so arguments like args.llmisvc_name and context.target_namespace are
passed as separate list elements instead of being interpolated into a shell
string.

---

Outside diff comments:
In `@projects/core/dsl/runner.py`:
- Around line 38-64: The command trace is split between logger.info and print
calls in projects/core/dsl/runner.py (around the subprocess.run / CommandResult
handling); replace the direct print(...) calls that output stdout, stderr and
exit code with logger.info(...) calls (preserving the same formatted strings and
.strip() usage) and also replace the trailing print() with logger.info("") so
the entire banner and command output go through the same logging path (use the
existing logger variable and keep constructing CommandResult as-is).

---

Duplicate comments:
In `@projects/core/library/vault.py`:
- Around line 382-397: The convenience functions validate_project_vaults and
get_vault_content_path currently call get_vault_manager() which can return None
if init() wasn't called; update both functions to guard against a None vault
manager by retrieving vm = get_vault_manager(), checking if vm is None and
raising a clear runtime error (e.g., "Vault manager not initialized; call init()
first") instead of calling methods on None, then delegate to
vm.validate_project_vaults(...) and vm.get_vault_content_path(...) when present;
ensure the error message names the function
(validate_project_vaults/get_vault_content_path) and suggests calling init() to
help debugging.

---

Nitpick comments:
In `@docs/orchestration/vaults.md`:
- Around line 158-165: The fenced code block showing the directory structure is
missing a language identifier; update the triple-backtick fence around the
directory tree in the vaults.md content to include a language tag (use "text")
so the block becomes ```text ... ``` which ensures correct rendering and
preserves spacing for the directory structure.
- Around line 84-91: Add language identifiers to the two fenced code blocks that
show path resolution examples in the docs/orchestration/vaults.md snippet:
change the opening triple-backtick fences for the examples containing
"{env_var_value}/{filename}" and "/secrets/ci/kubeconfig" to include a language
identifier such as "text" (i.e., ```text) so linters and formatters recognize
them as plaintext code blocks.
- Around line 265-291: In the "Error Handling" section (the four error example
blocks under the header), add explicit language identifiers to each fenced code
block by changing the opening triple backticks to ```text for the examples:
"Vault 'my-vault' requires environment variable MY_VAULT_SECRET_PATH to be set",
"Vault 'my-vault' content 'api_key' is missing description", "Vault 'my-vault'
contains extra file 'backup.txt' at '/path/vault/backup.txt' not defined in
specification", and "Vault 'my-vault' missing content 'api_key' at:
/path/vault/api_key" so they render with the text language tag and consistent
formatting.

In `@projects/core/dsl/template.py`:
- Around line 76-84: The autoescape parameter in the jinja2.Environment call is
misleading for YAML generation; update the env construction (the
jinja2.Environment call that currently references templates_dir and autoescape)
by removing the autoescape argument and its misleading comment (or set
autoescape=None) so the configuration and comment accurately reflect that
auto-escaping is not used for YAML; keep the other options
(undefined=jinja2.StrictUndefined, keep_trailing_newline=True) unchanged.

In `@projects/core/library/vault.py`:
- Line 68: The type hints use implicit Optional (e.g., vault_definitions_dir:
Path = None in Vault.__init__) which violates PEP 484; update the signatures to
use explicit Optional[Path] and import Optional from typing (add "from typing
import Optional" if missing), and apply the same change to the other occurrence
referenced around line 360 (replace the parameter currently annotated as Path =
None with Optional[Path]). Ensure imports are updated and any type references
use Optional[Path] consistently.

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py`:
- Line 28: The parameter declaration using a nullable default (env: dict = None)
violates PEP 484; change the annotation to use explicit Optional by updating the
parameter to env: Optional[dict] = None and add Optional to your typing imports
(e.g., include Optional in the existing from typing import ... list or add from
typing import Optional); ensure you update the signature where the env parameter
is defined so tools and linters recognize the optional type.
- Line 90: Replace the unnecessary f-string prefix on the return statement that
currently reads return f"Inputs validated" with a plain string literal (return
"Inputs validated") so no formatting is used; locate the return f"Inputs
validated" in the function that performs input validation and remove the leading
f character.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c61be165-bf61-487d-aa18-faed30df58ea

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf6b86 and cb94be2.

📒 Files selected for processing (19)
  • docs/orchestration/vaults.md
  • projects/core/dsl/__init__.py
  • projects/core/dsl/context.py
  • projects/core/dsl/runner.py
  • projects/core/dsl/runtime.py
  • projects/core/dsl/shell.py
  • projects/core/dsl/template.py
  • projects/core/dsl/toolbox.py
  • projects/core/library/ci.py
  • projects/core/library/config.py
  • projects/core/library/vault.py
  • projects/fournos_launcher/orchestration/ci.py
  • projects/fournos_launcher/orchestration/config.yaml
  • projects/fournos_launcher/orchestration/submit.py
  • projects/fournos_launcher/toolbox/submit_and_wait/main.py
  • projects/llm_d/orchestration/ci.py
  • projects/llm_d/orchestration/vaults.yaml
  • projects/llm_d/toolbox/capture_isvc_state/main.py
  • projects/skeleton/orchestration/ci.py
💤 Files with no reviewable changes (1)
  • projects/llm_d/orchestration/vaults.yaml
✅ Files skipped from review due to trivial changes (2)
  • projects/fournos_launcher/orchestration/ci.py
  • projects/core/dsl/init.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • projects/skeleton/orchestration/ci.py
  • projects/llm_d/orchestration/ci.py
  • projects/fournos_launcher/orchestration/config.yaml

Comment thread projects/core/dsl/context.py
Comment thread projects/core/dsl/toolbox.py
Comment thread projects/fournos_launcher/orchestration/submit.py
Comment thread projects/llm_d/toolbox/capture_isvc_state/main.py
Comment thread projects/llm_d/toolbox/capture_isvc_state/main.py
@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Copy link
Copy Markdown

@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 (2)
projects/core/dsl/toolbox.py (1)

107-109: ⚠️ Potential issue | 🟠 Major

clean_args() will crash when task_context is None.

The clean_args function iterates over value.items() (line 93), but e.task_context can be None when task context is unavailable (see TaskExecutionError.__init__ default). This will raise AttributeError: 'NoneType' object has no attribute 'items' during error reporting.

Also, the f-string on line 107 has no placeholders.

🐛 Proposed fix
-    yield f"~~ CONTEXT:"
-    for line in yaml.dump(clean_args(e.task_context), default_flow_style=False, sort_keys=False).splitlines():
-        yield f"~~     {line}"
+    yield "~~ CONTEXT:"
+    if e.task_context is not None:
+        for line in yaml.dump(clean_args(e.task_context), default_flow_style=False, sort_keys=False).splitlines():
+            yield f"~~     {line}"
+    else:
+        yield "~~     (no context available)"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/core/dsl/toolbox.py` around lines 107 - 109, The code currently
calls clean_args(e.task_context) which will throw if e.task_context is None
because clean_args expects a mapping; change to pass a safe default (e.g.
clean_args(e.task_context or {})) or explicitly check if e.task_context is None
and yield a placeholder like "~~     <no context available>" before dumping;
also remove the unnecessary f-string on the "~~ CONTEXT:" line (use a plain
string) so the yield reads consistently and avoid the AttributeError when
TaskExecutionError.__init__ leaves task_context as None.
projects/llm_d/toolbox/capture_isvc_state/main.py (1)

14-18: ⚠️ Potential issue | 🟠 Major

Update all callers to pass namespace as a keyword argument.

The * in the function signature makes namespace keyword-only. Both test_llmd.py files pass it positionally at line 27:

capture_isvc_state(_cfg.name, _cfg.ns)  # ❌ Will fail

Must be:

capture_isvc_state(_cfg.name, namespace=_cfg.ns)  # ✓ Correct

Update both locations:

  • projects/llm_d/orchestration/test_llmd.py:27
  • projects/fournos_launcher/orchestration/test_llmd.py:27
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/llm_d/toolbox/capture_isvc_state/main.py` around lines 14 - 18, The
function run(...) makes namespace a keyword-only parameter, so callers must pass
it by name; update all calls to capture_isvc_state that currently pass namespace
positionally (e.g., capture_isvc_state(_cfg.name, _cfg.ns)) to use a keyword
argument instead (capture_isvc_state(_cfg.name, namespace=_cfg.ns)); search for
calls to capture_isvc_state (and any direct calls to run(...)) in the tests and
update them accordingly.
🧹 Nitpick comments (8)
projects/core/library/ci.py (1)

110-131: Consider refactoring to reduce code duplication.

The safe_ci_function decorator shares nearly identical implementation with safe_ci_command (lines 87-108), differing only in success-path behavior. This duplication increases maintenance burden.

♻️ Proposed refactor to extract common logic
+def _safe_wrapper_base(command_func, exit_on_success=True):
+    """
+    Base wrapper for CI commands with consistent error handling.
+    
+    Args:
+        command_func: Function to execute safely
+        exit_on_success: Whether to call sys.exit on successful execution
+    """
+    def wrapper(*args, **kwargs):
+        try:
+            result = command_func(*args, **kwargs)
+            if exit_on_success:
+                sys.exit(result)
+            return result
+        except Exception as e:
+            handle_ci_exception(e)
+            sys.exit(1)
+    
+    # Preserve original function metadata
+    wrapper.__name__ = command_func.__name__
+    wrapper.__doc__ = command_func.__doc__
+    wrapper.__module__ = command_func.__module__
+    wrapper.__qualname__ = command_func.__qualname__
+    
+    return wrapper
+
 def safe_ci_command(command_func):
     """
-    Decorator/wrapper for CI commands to provide consistent error handling.
+    Decorator for CI commands that exits the process with the command's return code.
+    
+    Use this for standalone CI scripts that should exit immediately.
 
     Args:
         command_func: Function to execute safely
     """
-    def wrapper(*args, **kwargs):
-        try:
-            exit_code = command_func(*args, **kwargs)
-            sys.exit(exit_code)
-        except Exception as e:
-            handle_ci_exception(e)
-            sys.exit(1)
-
-    # Preserve original function metadata
-    wrapper.__name__ = command_func.__name__
-    wrapper.__doc__ = command_func.__doc__
-    wrapper.__module__ = command_func.__module__
-    wrapper.__qualname__ = command_func.__qualname__
-
-    return wrapper
+    return _safe_wrapper_base(command_func, exit_on_success=True)

 def safe_ci_function(command_func):
     """
-    Decorator/wrapper for CI commands to provide consistent error handling.
-    This version does NOT exit on success.
+    Decorator for CI functions that returns the result without exiting.
+    
+    Use this for Click-based entrypoints or functions called by other code.
 
     Args:
         command_func: Function to execute safely
     """
-    def wrapper(*args, **kwargs):
-        try:
-            return command_func(*args, **kwargs)
-        except Exception as e:
-            handle_ci_exception(e)
-            sys.exit(1)
-
-    # Preserve original function metadata
-    wrapper.__name__ = command_func.__name__
-    wrapper.__doc__ = command_func.__doc__
-    wrapper.__module__ = command_func.__module__
-    wrapper.__qualname__ = command_func.__qualname__
-
-    return wrapper
+    return _safe_wrapper_base(command_func, exit_on_success=False)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/ci.py` around lines 110 - 131, The two decorators
safe_ci_function and safe_ci_command duplicate nearly identical logic; refactor
by extracting a factory like _make_safe_ci_decorator(exit_on_success: bool) that
builds the inner wrapper, uses try/except to call command_func and
handle_ci_exception(e), and conditionally calls sys.exit(0) on success or
sys.exit(1) on failure according to the exit_on_success flag; then reimplement
safe_ci_function = _make_safe_ci_decorator(exit_on_success=False) and
safe_ci_command = _make_safe_ci_decorator(exit_on_success=True), preserving
function metadata (wrapper.__name__, __doc__, __module__, __qualname__) and
original behavior of handle_ci_exception and sys.exit calls.
docs/orchestration/vaults.md (2)

112-126: Consider adding initialization note to validation example.

The validation example at lines 112-126 assumes vault.init() has already been called. Adding a brief comment or moving this after the "Initialization" section (line 233) would improve clarity for readers following top-to-bottom.

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

In `@docs/orchestration/vaults.md` around lines 112 - 126, The validation example
uses vault_manager (from get_vault_manager) but omits calling vault.init();
update the example to either add a short comment above it noting that
vault.init() must be called first or move the entire snippet to follow the
existing Initialization section; specifically reference get_vault_manager and
vault_manager.validate_vault / validate_project_vaults / validate_all_vaults so
readers know to run vault.init() before those validation calls.

84-91: Add language specifiers to fenced code blocks.

Several code blocks are missing language specifiers, which affects syntax highlighting and linting compliance.

♻️ Proposed fix for path resolution examples
-```
+```text
 {env_var_value}/{filename}

...
- +text
/secrets/ci/kubeconfig

Apply similar fixes to code blocks at lines 158, 270, 276, 282, and 288.

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

In `@docs/orchestration/vaults.md` around lines 84 - 91, The fenced code blocks
that show path examples such as "{env_var_value}/{filename}" and
"/secrets/ci/kubeconfig" (and the other similar path/example blocks) lack
language specifiers; update each triple-backtick fence to include a language
hint (use "text") — e.g. change ``` to ```text for the shown path example blocks
and the other similar code fences (the ones with path/example content) so they
get proper syntax highlighting and satisfy linting.
projects/core/library/vault.py (2)

84-86: Consider catching specific exceptions for vault loading.

Catching bare Exception is flagged by static analysis. For file loading, consider catching specific exceptions like yaml.YAMLError, FileNotFoundError, and PermissionError for clearer error handling.

♻️ Optional: More specific exception handling
-            except Exception as e:
+            except (yaml.YAMLError, OSError, KeyError) as e:
                 logger.error(f"Failed to load vault definition from {vault_file}: {e}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/core/library/vault.py` around lines 84 - 86, Replace the broad
"except Exception as e" in the vault loading code with specific exception
handlers so failures are classified and logged properly: catch yaml.YAMLError
for YAML parsing issues, FileNotFoundError for missing vault_file,
PermissionError for access problems (and optionally OSError for other IO
errors), and log each with the vault_file and error details; keep a small final
generic except to re-raise or log unexpected exceptions. Target the try/except
block around the vault loading function (the code that reads/parses vault_file
in projects/core/library/vault.py).

68-68: Use explicit | None type hints for optional parameters.

Static analysis flags implicit Optional on lines 68 and 360.

♻️ Proposed fix
-    def __init__(self, vault_definitions_dir: Path = None):
+    def __init__(self, vault_definitions_dir: Path | None = None):
...
-def init(vaults: List[str] = None):
+def init(vaults: list[str] | None = None):

Also applies to: 360-360

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

In `@projects/core/library/vault.py` at line 68, Update the optional parameter
type hints to explicitly include None: change the signature of Vault.__init__
(the method shown as def __init__(self, vault_definitions_dir: Path = None)) to
use the new union syntax (vault_definitions_dir: Path | None = None) and
likewise update the other optional parameter at the other occurrence on line 360
to use the explicit `| None` annotation; ensure imports/types remain correct and
mypy/flake8 will treat these as explicit Optionals.
projects/core/dsl/runtime.py (1)

27-34: Use explicit | None type hints for optional parameters.

The function signature uses implicit Optional types (e.g., task_args: dict = None), which is prohibited by PEP 484 and flagged by static analysis. Use explicit union types for clarity.

♻️ Proposed fix
-    def __init__(self, task_name: str, task_description: str, original_exception: Exception, task_args: dict = None, task_location: str = None, artifact_dir: str = None, task_context: dict = None):
+    def __init__(self, task_name: str, task_description: str, original_exception: Exception, task_args: dict | None = None, task_location: str | None = None, artifact_dir: str | None = None, task_context: dict | None = None):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/core/dsl/runtime.py` around lines 27 - 34, Update the __init__
signature to use explicit union type hints for the optional parameters instead
of implicit Optional defaulting to None: change task_args: dict = None to
task_args: dict | None = None, task_location: str = None to task_location: str |
None = None, artifact_dir: str = None to artifact_dir: str | None = None, and
task_context: dict = None to task_context: dict | None = None in the __init__
method so static analyzers accept the explicit None-able types while leaving
required params (task_name, task_description, original_exception) unchanged.
projects/fournos_launcher/toolbox/submit_and_wait/main.py (2)

28-28: Use explicit | None type hint.

Static analysis flags implicit Optional. Use env: dict | None = None for PEP 484 compliance.

♻️ Proposed fix
-    env: dict = None,
+    env: dict | None = None,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py` at line 28, The
parameter type for env currently uses an implicit Optional (env: dict = None);
update its annotation to an explicit union by changing it to env: dict | None =
None (e.g., in the function signature where env is declared) so static analysis
recognizes the Optional type per PEP 484.

90-90: Remove extraneous f prefix from string literal.

The string has no placeholders, so the f prefix is unnecessary.

♻️ Proposed fix
-    return f"Inputs validated"
+    return "Inputs validated"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py` at line 90, Remove
the unnecessary f-string prefix from the return statement that currently reads
return f"Inputs validated" in main.py; update that return to be a plain string
literal (i.e., drop the leading "f") so the function returns a normal string
without formatting overhead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@projects/core/dsl/shell.py`:
- Line 81: Several logger.info calls use unnecessary f-string prefixes (Ruff
F541); remove the leading f from these placeholder-free messages. Locate the
logger.info calls that log literal messages like "| <stdout not logged>", "|
<stderr not logged>", "No shell available" (the calls around the reported lines)
and change them from f"... " to plain string literals "..." so there is no
f-prefix on strings with no interpolation.

In `@projects/core/dsl/template.py`:
- Around line 46-64: The template auto-detection and _get_task_context() are
looking for a local variable named 'args' but the runtime passes
'readonly_args'; update the detection logic in the template.py functions (the
loop that assigns context_var and decides caller_file/templates_dir) and in
_get_task_context() to check for both 'args' and 'readonly_args' (e.g., if
'args' in local_vars or 'readonly_args' in local_vars, use that value) so the
task frame is correctly recognized and templates_dir/caller_file are set from
the task frame.

---

Duplicate comments:
In `@projects/core/dsl/toolbox.py`:
- Around line 107-109: The code currently calls clean_args(e.task_context) which
will throw if e.task_context is None because clean_args expects a mapping;
change to pass a safe default (e.g. clean_args(e.task_context or {})) or
explicitly check if e.task_context is None and yield a placeholder like "~~    
<no context available>" before dumping; also remove the unnecessary f-string on
the "~~ CONTEXT:" line (use a plain string) so the yield reads consistently and
avoid the AttributeError when TaskExecutionError.__init__ leaves task_context as
None.

In `@projects/llm_d/toolbox/capture_isvc_state/main.py`:
- Around line 14-18: The function run(...) makes namespace a keyword-only
parameter, so callers must pass it by name; update all calls to
capture_isvc_state that currently pass namespace positionally (e.g.,
capture_isvc_state(_cfg.name, _cfg.ns)) to use a keyword argument instead
(capture_isvc_state(_cfg.name, namespace=_cfg.ns)); search for calls to
capture_isvc_state (and any direct calls to run(...)) in the tests and update
them accordingly.

---

Nitpick comments:
In `@docs/orchestration/vaults.md`:
- Around line 112-126: The validation example uses vault_manager (from
get_vault_manager) but omits calling vault.init(); update the example to either
add a short comment above it noting that vault.init() must be called first or
move the entire snippet to follow the existing Initialization section;
specifically reference get_vault_manager and vault_manager.validate_vault /
validate_project_vaults / validate_all_vaults so readers know to run
vault.init() before those validation calls.
- Around line 84-91: The fenced code blocks that show path examples such as
"{env_var_value}/{filename}" and "/secrets/ci/kubeconfig" (and the other similar
path/example blocks) lack language specifiers; update each triple-backtick fence
to include a language hint (use "text") — e.g. change ``` to ```text for the
shown path example blocks and the other similar code fences (the ones with
path/example content) so they get proper syntax highlighting and satisfy
linting.

In `@projects/core/dsl/runtime.py`:
- Around line 27-34: Update the __init__ signature to use explicit union type
hints for the optional parameters instead of implicit Optional defaulting to
None: change task_args: dict = None to task_args: dict | None = None,
task_location: str = None to task_location: str | None = None, artifact_dir: str
= None to artifact_dir: str | None = None, and task_context: dict = None to
task_context: dict | None = None in the __init__ method so static analyzers
accept the explicit None-able types while leaving required params (task_name,
task_description, original_exception) unchanged.

In `@projects/core/library/ci.py`:
- Around line 110-131: The two decorators safe_ci_function and safe_ci_command
duplicate nearly identical logic; refactor by extracting a factory like
_make_safe_ci_decorator(exit_on_success: bool) that builds the inner wrapper,
uses try/except to call command_func and handle_ci_exception(e), and
conditionally calls sys.exit(0) on success or sys.exit(1) on failure according
to the exit_on_success flag; then reimplement safe_ci_function =
_make_safe_ci_decorator(exit_on_success=False) and safe_ci_command =
_make_safe_ci_decorator(exit_on_success=True), preserving function metadata
(wrapper.__name__, __doc__, __module__, __qualname__) and original behavior of
handle_ci_exception and sys.exit calls.

In `@projects/core/library/vault.py`:
- Around line 84-86: Replace the broad "except Exception as e" in the vault
loading code with specific exception handlers so failures are classified and
logged properly: catch yaml.YAMLError for YAML parsing issues, FileNotFoundError
for missing vault_file, PermissionError for access problems (and optionally
OSError for other IO errors), and log each with the vault_file and error
details; keep a small final generic except to re-raise or log unexpected
exceptions. Target the try/except block around the vault loading function (the
code that reads/parses vault_file in projects/core/library/vault.py).
- Line 68: Update the optional parameter type hints to explicitly include None:
change the signature of Vault.__init__ (the method shown as def __init__(self,
vault_definitions_dir: Path = None)) to use the new union syntax
(vault_definitions_dir: Path | None = None) and likewise update the other
optional parameter at the other occurrence on line 360 to use the explicit `|
None` annotation; ensure imports/types remain correct and mypy/flake8 will treat
these as explicit Optionals.

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py`:
- Line 28: The parameter type for env currently uses an implicit Optional (env:
dict = None); update its annotation to an explicit union by changing it to env:
dict | None = None (e.g., in the function signature where env is declared) so
static analysis recognizes the Optional type per PEP 484.
- Line 90: Remove the unnecessary f-string prefix from the return statement that
currently reads return f"Inputs validated" in main.py; update that return to be
a plain string literal (i.e., drop the leading "f") so the function returns a
normal string without formatting overhead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cbe31401-7edb-45bb-82fa-2cc4054c9f5d

📥 Commits

Reviewing files that changed from the base of the PR and between cb94be2 and 3421b72.

📒 Files selected for processing (17)
  • docs/orchestration/vaults.md
  • projects/core/dsl/__init__.py
  • projects/core/dsl/context.py
  • projects/core/dsl/runner.py
  • projects/core/dsl/runtime.py
  • projects/core/dsl/shell.py
  • projects/core/dsl/template.py
  • projects/core/dsl/toolbox.py
  • projects/core/library/ci.py
  • projects/core/library/vault.py
  • projects/fournos_launcher/orchestration/ci.py
  • projects/fournos_launcher/orchestration/config.yaml
  • projects/fournos_launcher/orchestration/submit.py
  • projects/fournos_launcher/toolbox/submit_and_wait/main.py
  • projects/llm_d/orchestration/ci.py
  • projects/llm_d/toolbox/capture_isvc_state/main.py
  • projects/skeleton/orchestration/ci.py
✅ Files skipped from review due to trivial changes (3)
  • projects/fournos_launcher/orchestration/ci.py
  • projects/core/dsl/init.py
  • projects/fournos_launcher/orchestration/config.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • projects/skeleton/orchestration/ci.py
  • projects/core/dsl/runner.py
  • projects/llm_d/orchestration/ci.py
  • projects/core/dsl/context.py

Comment thread projects/core/dsl/shell.py
Comment thread projects/core/dsl/template.py
@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Failure indicator:

## /logs/artifacts/FAILURE 
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
~~ projects/fournos_launcher/toolbox/submit_and_wait/main.py:127
~~ TASK: submit_fournos_job: Submit the FOURNOS job
~~ ARTIFACT_DIR: /logs/artifacts/001__submit_and_wait
~~ LOG_FILE: /logs/artifacts/001__submit_and_wait/task.log
~~ ARGS:
~~     cluster_name: psap
~~     project: llm_d
~~     args:
~~     - hello
~~     - world
~~     variables_overrides:
~~       something.is.wrong: 'false'
~~     job_name: ''
~~     namespace: psap-automation
~~     owner: fournos-launcher
~~     display_name: Forge Job
~~     pipeline_name: forge-test-only
~~     env:
~~       JOB_TYPE: presubmit
~~       ENTRYPOINT_OPTIONS: '{"timeout":82800000000000,"grace_period":15000000000,"artifact_dir":"/logs/artifacts","args":["/bin/bash","-c","#!/bin/bash\nset
~~         -eu\nexport PSAP_FORGE_FOURNOS_CI_SECRET_PATH=/var/run/psap-forge-ci\nexport PSAP_FORGE_NOTIFICATIONS_SECRET_PATH=/var/run/psap-forge-notifications\n\ngit
~~         -C $HOME fetch --quiet origin $PULL_PULL_SHA\ngit -C $HOME reset --hard FETCH_HEAD\n\nexec
~~         ./bin/run_ci fournos ci submit\n"],"container_name":"test","process_log":"/logs/process-log.txt","marker_file":"/logs/marker-file.txt","metadata_file":"/logs/artifacts/metadata.json"}'
~~       JOB_NAME: pull-ci-openshift-psap-forge-main-fournos
~~       JOB_SPEC: '{"type":"presubmit","job":"pull-ci-openshift-psap-forge-main-fournos","buildid":"2041887157357907968","prowjobid":"7b3e4c79-d40d-48fe-9348-4555b22ccc7a","refs":{"org":"openshift-psap","repo":"forge","repo_link":"https://github.com/openshift-psap/forge","base_ref":"main","base_sha":"53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9","base_link":"https://github.com/openshift-psap/forge/commit/53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9","pulls":[{"number":18,"author":"kpouget","sha":"2e47b5b29fd6435efce1818a5c526b4111cc281c","title":"[forge]
~~         Keep working on the framework","head_ref":"launcher","link":"https://github.com/openshift-psap/forge/pull/18","commit_link":"https://github.com/openshift-psap/forge/pull/18/commits/2e47b5b29fd6435efce1818a5c526b4111cc281c","author_link":"https://github.com/kpouget"}]},"decoration_config":{"timeout":"23h0m0s","grace_period":"15s","utility_images":{"clonerefs":"us-docker.pkg.dev/k8s-infra-prow/images/clonerefs:v20260328-dc2bbc9fc","initupload":"us-docker.pkg.dev/k8s-infra-prow/images/initupload:v20260328-dc2bbc9fc","entrypoint":"us-docker.pkg.dev/k8s-infra-prow/images/entrypoint:v20260328-dc2bbc9fc","sidecar":"us-docker.pkg.dev/k8s-infra-prow/images/sidecar:v20260328-dc2bbc9fc"},"resources":{"clonerefs":{"limits":{"memory":"3Gi"},"requests":{"cpu":"100m","memory":"500Mi"}},"initupload":{"limits":{"memory":"200Mi"},"requests":{"cpu":"100m","memory":"50Mi"}},"place_entrypoint":{"limits":{"memory":"100Mi"},"requests":{"cpu":"100m","memory":"25Mi"}},"sidecar":{"limits":{"memory":"2Gi"},"requests":{"cpu":"100m","memory":"250Mi"}}},"gcs_configuration":{"bucket":"test-platform-results","path_strategy":"single","default_org":"openshift","default_repo":"origin","mediaTypes":{"log":"text/plain"},"compress_file_types":["txt","log","json","tar","html","yaml"]},"gcs_credentials_secret":"gce-sa-credentials-gcs-publisher","skip_cloning":true,"censor_secrets":true,"censoring_options":{"minimum_secret_length":6}}}'
~~       OPENSHIFT_CI: 'true'
~~       JOB_NAME_SAFE: fournos
~~       BUILD_ID: '2041887157357907968'
~~       PULL_PULL_SHA: 2e47b5b29fd6435efce1818a5c526b4111cc281c
~~       PULL_NUMBER: '18'
~~       PULL_BASE_REF: main
~~       REPO_NAME: forge
~~       REPO_OWNER: openshift-psap
~~       PULL_BASE_SHA: 53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9
~~       PULL_TITLE: '[forge] Keep working on the framework'
~~       PULL_REFS: main:53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9,18:2e47b5b29fd6435efce1818a5c526b4111cc281c
~~       PULL_HEAD_REF: launcher
~~     artifact_dir: /logs/artifacts/001__submit_and_wait
~~ CONTEXT:
~~     final_job_name: forge-llm_d-20260408-143354
~~     manifest_file: /logs/artifacts/001__submit_and_wait/src/forge-llm_d-20260408-143354-manifest.yaml
~~
~~ EXCEPTION: CalledProcessError
~~     Command 'oc apply -f /logs/artifacts/001__submit_and_wait/src/forge-llm_d-20260408-143354-manifest.yaml' returned non-zero exit status 1.
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


[...]

Execution logs

Copy link
Copy Markdown

@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.

♻️ Duplicate comments (2)
projects/fournos_launcher/orchestration/submit.py (2)

22-28: ⚠️ Potential issue | 🟡 Minor

Add validation for None kubeconfig path.

If get_vault_content_path returns None (vault not found or content not defined), str(kubeconfig_path) will produce the literal string "None", which sets an invalid KUBECONFIG path. This would cause downstream oc commands to fail with confusing errors.

🛡️ Proposed fix
 def prepare_env():
     kubeconfig_path = vault.get_vault_content_path(
         config.project.get_config("fournos.kubeconfig.vault.name"),
         config.project.get_config("fournos.kubeconfig.vault.key")
     )

+    if kubeconfig_path is None:
+        raise ValueError("Failed to retrieve kubeconfig path from vault")
+
     os.environ["KUBECONFIG"] = str(kubeconfig_path)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/fournos_launcher/orchestration/submit.py` around lines 22 - 28, In
prepare_env, validate the result of vault.get_vault_content_path
(kubeconfig_path) before setting os.environ["KUBECONFIG"]; if kubeconfig_path is
None, raise a clear exception or log an error and exit (e.g., raise
ValueError("kubeconfig not found in vault") or call sys.exit(1)) instead of
setting the literal "None"; update the prepare_env function to check `if
kubeconfig_path is None:` and handle the failure path, otherwise set
os.environ["KUBECONFIG"] = str(kubeconfig_path).

31-34: ⚠️ Potential issue | 🟡 Minor

Guard against None values from config lookups.

If overrides or extra_overrides keys return None from the config, calling .update(None) will raise a TypeError. While overrides is set by filter_out_used_overrides(), extra_overrides might not be defined.

🛡️ Proposed fix
 def submit_job():
     overrides = {}
-    overrides.update(config.project.get_config("overrides"))
-    overrides.update(config.project.get_config("extra_overrides"))
+    overrides.update(config.project.get_config("overrides") or {})
+    overrides.update(config.project.get_config("extra_overrides") or {})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/fournos_launcher/orchestration/submit.py` around lines 31 - 34, The
submit_job function calls overrides.update(...) with values from
config.project.get_config("overrides") and get_config("extra_overrides") which
may be None; change the logic so you coerce or guard those lookups to an empty
dict before calling update (e.g., assign tmp =
config.project.get_config("overrides") or {} and similarly for
"extra_overrides", or only call overrides.update(...) if the returned value is a
dict), ensuring overrides is always a dict and avoiding TypeError in submit_job.
🧹 Nitpick comments (3)
projects/llm_d/orchestration/test_llmd.py (1)

25-27: Consider removing unused variable name.

The variable name assigned on line 25 is never used. While the comment indicates it demonstrates an alternative config query approach, leaving unused variables in the code can be confusing. Consider either removing it or using it in place of _cfg.name for consistency.

🧹 Suggested cleanup
-    name = config.project.get_config("tests.llmd.flavors")
-
-    capture_isvc_state(_cfg.name, namespace=_cfg.ns)
+    capture_isvc_state(_cfg.name, namespace=_cfg.ns)

Or, if you want to keep the demonstration:

     name = config.project.get_config("tests.llmd.flavors")
 
-    capture_isvc_state(_cfg.name, namespace=_cfg.ns)
+    capture_isvc_state(name, namespace=_cfg.ns)  # using the alternative config query
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/llm_d/orchestration/test_llmd.py` around lines 25 - 27, The local
variable name is assigned from config.project.get_config("tests.llmd.flavors")
but never used; either remove the unused assignment or use it consistently
(e.g., replace _cfg.name with name when calling capture_isvc_state) to eliminate
the dead variable and clarify intent—update the call site
capture_isvc_state(_cfg.name, namespace=_cfg.ns) and related references
accordingly or drop the name assignment entirely.
projects/fournos_launcher/toolbox/submit_and_wait/main.py (2)

90-90: Remove extraneous f prefix from string literal.

This f-string has no placeholders, so the f prefix is unnecessary.

♻️ Proposed fix
-    return f"Inputs validated"
+    return "Inputs validated"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py` at line 90, The
return statement currently uses an unnecessary f-string: change the return from
using an f-prefixed string to a plain string literal (remove the leading "f" on
the "return f\"Inputs validated\"" statement) so it becomes a normal string
return; locate the return in the function that contains "return f\"Inputs
validated\"" and replace it with "return \"Inputs validated\"" (no other logic
changes required).

28-28: Use explicit Optional type hint for env parameter.

PEP 484 prohibits implicit Optional. When a parameter has a default of None, the type hint should explicitly indicate it can be None.

♻️ Proposed fix
+from typing import Optional
+
 def run(
     cluster_name: str,
     project: str,
     *,
     args: list = None,
     variables_overrides: dict = None,
     job_name: str = "",
     namespace: str = "fournos-jobs",
     owner: str = "",
     display_name: str = "",
     pipeline_name: str = "",
-    env: dict = None,
+    env: Optional[dict] = None,
 ):

Note: The same issue applies to args and variables_overrides parameters on lines 21-22.

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

In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py` at line 28, The
parameters that default to None must use explicit Optional types: replace env:
dict = None with env: Optional[dict] = None, and do the same for args and
variables_overrides (e.g., args: Optional[list] = None, variables_overrides:
Optional[dict] = None) in the function signature where they are declared; also
add from typing import Optional at the top of the module so the names resolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@projects/fournos_launcher/orchestration/submit.py`:
- Around line 22-28: In prepare_env, validate the result of
vault.get_vault_content_path (kubeconfig_path) before setting
os.environ["KUBECONFIG"]; if kubeconfig_path is None, raise a clear exception or
log an error and exit (e.g., raise ValueError("kubeconfig not found in vault")
or call sys.exit(1)) instead of setting the literal "None"; update the
prepare_env function to check `if kubeconfig_path is None:` and handle the
failure path, otherwise set os.environ["KUBECONFIG"] = str(kubeconfig_path).
- Around line 31-34: The submit_job function calls overrides.update(...) with
values from config.project.get_config("overrides") and
get_config("extra_overrides") which may be None; change the logic so you coerce
or guard those lookups to an empty dict before calling update (e.g., assign tmp
= config.project.get_config("overrides") or {} and similarly for
"extra_overrides", or only call overrides.update(...) if the returned value is a
dict), ensuring overrides is always a dict and avoiding TypeError in submit_job.

---

Nitpick comments:
In `@projects/fournos_launcher/toolbox/submit_and_wait/main.py`:
- Line 90: The return statement currently uses an unnecessary f-string: change
the return from using an f-prefixed string to a plain string literal (remove the
leading "f" on the "return f\"Inputs validated\"" statement) so it becomes a
normal string return; locate the return in the function that contains "return
f\"Inputs validated\"" and replace it with "return \"Inputs validated\"" (no
other logic changes required).
- Line 28: The parameters that default to None must use explicit Optional types:
replace env: dict = None with env: Optional[dict] = None, and do the same for
args and variables_overrides (e.g., args: Optional[list] = None,
variables_overrides: Optional[dict] = None) in the function signature where they
are declared; also add from typing import Optional at the top of the module so
the names resolve.

In `@projects/llm_d/orchestration/test_llmd.py`:
- Around line 25-27: The local variable name is assigned from
config.project.get_config("tests.llmd.flavors") but never used; either remove
the unused assignment or use it consistently (e.g., replace _cfg.name with name
when calling capture_isvc_state) to eliminate the dead variable and clarify
intent—update the call site capture_isvc_state(_cfg.name, namespace=_cfg.ns) and
related references accordingly or drop the name assignment entirely.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d446b831-29a5-4dfa-a5ca-2b06bc2244de

📥 Commits

Reviewing files that changed from the base of the PR and between 3421b72 and 2e47b5b.

📒 Files selected for processing (8)
  • projects/core/dsl/toolbox.py
  • projects/fournos_launcher/orchestration/config.yaml
  • projects/fournos_launcher/orchestration/submit.py
  • projects/fournos_launcher/orchestration/test_llmd.py
  • projects/fournos_launcher/toolbox/submit_and_wait/main.py
  • projects/fournos_launcher/toolbox/submit_and_wait/templates/job.yaml.j2
  • projects/llm_d/orchestration/test_llmd.py
  • projects/llm_d/toolbox/capture_isvc_state/main.py
💤 Files with no reviewable changes (1)
  • projects/fournos_launcher/orchestration/test_llmd.py
✅ Files skipped from review due to trivial changes (1)
  • projects/fournos_launcher/toolbox/submit_and_wait/templates/job.yaml.j2
🚧 Files skipped from review as they are similar to previous changes (1)
  • projects/llm_d/toolbox/capture_isvc_state/main.py

@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

@topsail-bot
Copy link
Copy Markdown

topsail-bot bot commented Apr 8, 2026

• Link to the test results.

• No reports index generated...

Test configuration:

/test fournos llm_d hello world
/cluster psap
/var something.is.wrong: false

Failure indicator:

## /logs/artifacts/FAILURE 
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
~~ projects/fournos_launcher/toolbox/submit_and_wait/main.py:127
~~ TASK: submit_fournos_job: Submit the FOURNOS job
~~ ARTIFACT_DIR: /logs/artifacts/001__submit_and_wait
~~ LOG_FILE: /logs/artifacts/001__submit_and_wait/task.log
~~ ARGS:
~~     cluster_name: psap
~~     project: llm_d
~~     args:
~~     - hello
~~     - world
~~     variables_overrides:
~~       something.is.wrong: 'false'
~~     job_name: ''
~~     namespace: psap-automation
~~     owner: fournos-launcher
~~     display_name: Forge Job
~~     pipeline_name: forge-test-only
~~     env:
~~       JOB_TYPE: presubmit
~~       JOB_NAME: pull-ci-openshift-psap-forge-main-fournos
~~       JOB_SPEC: '{"type":"presubmit","job":"pull-ci-openshift-psap-forge-main-fournos","buildid":"2041890899750817792","prowjobid":"a90edde9-f90e-4ad6-ad4b-a06155d32add","refs":{"org":"openshift-psap","repo":"forge","repo_link":"https://github.com/openshift-psap/forge","base_ref":"main","base_sha":"53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9","base_link":"https://github.com/openshift-psap/forge/commit/53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9","pulls":[{"number":18,"author":"kpouget","sha":"2b10456873b17393bf71afa0565bc38ad4f686e1","title":"[forge]
~~         Keep working on the framework","head_ref":"launcher","link":"https://github.com/openshift-psap/forge/pull/18","commit_link":"https://github.com/openshift-psap/forge/pull/18/commits/2b10456873b17393bf71afa0565bc38ad4f686e1","author_link":"https://github.com/kpouget"}]},"decoration_config":{"timeout":"23h0m0s","grace_period":"15s","utility_images":{"clonerefs":"us-docker.pkg.dev/k8s-infra-prow/images/clonerefs:v20260328-dc2bbc9fc","initupload":"us-docker.pkg.dev/k8s-infra-prow/images/initupload:v20260328-dc2bbc9fc","entrypoint":"us-docker.pkg.dev/k8s-infra-prow/images/entrypoint:v20260328-dc2bbc9fc","sidecar":"us-docker.pkg.dev/k8s-infra-prow/images/sidecar:v20260328-dc2bbc9fc"},"resources":{"clonerefs":{"limits":{"memory":"3Gi"},"requests":{"cpu":"100m","memory":"500Mi"}},"initupload":{"limits":{"memory":"200Mi"},"requests":{"cpu":"100m","memory":"50Mi"}},"place_entrypoint":{"limits":{"memory":"100Mi"},"requests":{"cpu":"100m","memory":"25Mi"}},"sidecar":{"limits":{"memory":"2Gi"},"requests":{"cpu":"100m","memory":"250Mi"}}},"gcs_configuration":{"bucket":"test-platform-results","path_strategy":"single","default_org":"openshift","default_repo":"origin","mediaTypes":{"log":"text/plain"},"compress_file_types":["txt","log","json","tar","html","yaml"]},"gcs_credentials_secret":"gce-sa-credentials-gcs-publisher","skip_cloning":true,"censor_secrets":true,"censoring_options":{"minimum_secret_length":6}}}'
~~       OPENSHIFT_CI: 'true'
~~       JOB_NAME_SAFE: fournos
~~       BUILD_ID: '2041890899750817792'
~~       PULL_PULL_SHA: 2b10456873b17393bf71afa0565bc38ad4f686e1
~~       PULL_NUMBER: '18'
~~       PULL_BASE_REF: main
~~       REPO_NAME: forge
~~       REPO_OWNER: openshift-psap
~~       PULL_BASE_SHA: 53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9
~~       PULL_TITLE: '[forge] Keep working on the framework'
~~       PULL_REFS: main:53a66cc71b6db86bcfcad5fbf0fcc7af41d92bc9,18:2b10456873b17393bf71afa0565bc38ad4f686e1
~~       PULL_HEAD_REF: launcher
~~     artifact_dir: /logs/artifacts/001__submit_and_wait
~~ CONTEXT:
~~     final_job_name: forge-llm_d-20260408-144949
~~     manifest_file: /logs/artifacts/001__submit_and_wait/src/forge-llm_d-20260408-144949-manifest.yaml
~~
~~ EXCEPTION: CalledProcessError
~~     Command 'oc apply -f /logs/artifacts/001__submit_and_wait/src/forge-llm_d-20260408-144949-manifest.yaml' returned non-zero exit status 1.
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


[...]

Execution logs

@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 8, 2026

@kpouget: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/fournos 2b10456 link true /test fournos

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@kpouget
Copy link
Copy Markdown
Contributor Author

kpouget commented Apr 8, 2026

@kpouget kpouget merged commit b6ae08d into openshift-psap:main Apr 8, 2026
3 of 5 checks passed
@kpouget kpouget deleted the launcher branch April 8, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant