Skip to content

Enhance project features, UI improvements, and documentation updates - #7

Merged
Rishabh-Bajpai merged 30 commits into
developmentfrom
rishabh-development
May 20, 2026
Merged

Enhance project features, UI improvements, and documentation updates#7
Rishabh-Bajpai merged 30 commits into
developmentfrom
rishabh-development

Conversation

@Rishabh-Bajpai

@Rishabh-Bajpai Rishabh-Bajpai commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added project file browser with tree view, directory navigation, file preview, and archive download
    • Added Git diff viewer showing staged, modified, and untracked changes
    • Added question workflow with reply/reject UI for agent interactions
    • Added notification settings with browser and ntfy support
    • Added scheduled task management with PRD tracking (Ralph Loop)
    • Added session management features: create, switch, delete, compact, and summarize
    • Added voice controls for transcription (STT) and text-to-speech (TTS)
    • Added runtime model and agent selection UI
  • Improvements

    • Expanded API with comprehensive endpoint documentation
    • Enhanced README with setup instructions and feature overview
    • Improved message rendering with GitHub-flavored Markdown support
    • Added token accounting visibility for AI model interactions

Review Change Stack

…s with enhanced output formatting and file path resolution.
… messages by adding horizontal scrolling wrappers
…rontend support for tracking context usage and compaction updates.
…d approvals, and add debouncing for assistant message notifications
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bfed069e-078e-4d9f-8da3-84512d6c3dd4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rishabh-development

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (19)
backend/app/routes/files.py-47-49 (1)

47-49: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid broad catches that expose internal errors to clients.

Line 47 and Line 78 catch Exception and return raw exception details in JSON. This can leak server internals and hide real bugs. Catch expected filesystem errors and return a generic 500 payload.

Proposed fix
-    except Exception as exc:
-        return jsonify({"error": f"Failed to list project files: {exc}"}), 500
+    except OSError:
+        return jsonify({"error": "Failed to list project files"}), 500
-    except Exception as exc:
-        return jsonify({"error": f"Failed to list directory: {exc}"}), 500
+    except OSError:
+        return jsonify({"error": "Failed to list directory"}), 500

Also applies to: 78-80

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

In `@backend/app/routes/files.py` around lines 47 - 49, The except blocks that
currently catch broad Exception and return the raw exception in JSON (the
handlers that return {"error": f"Failed to list project files: {exc}"} and the
similar block at lines ~78) should be replaced with specific exception handling:
catch expected filesystem errors (e.g., FileNotFoundError, PermissionError,
OSError) and for any unexpected errors let them bubble or be handled by a global
error handler; in each specific except, log the full exception details
server-side using the app logger (e.g., app.logger.exception or
logger.exception) and return a generic JSON error like {"error": "Failed to list
project files"} with status 500 without embedding exc; keep the same
function/context (the handler that produces "Failed to list project files" and
the other handler at ~78) when implementing these changes.
backend/app/routes/helpers.py-634-651 (1)

634-651: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t fail hard on stale cached last_session_id.

Line 649 currently raises when get_session fails for any candidate, including cached project.last_session_id. That blocks fallback session resolution and can break projects with stale session pointers.

Proposed fix
-    had_candidate = candidate_session_id is not None
+    explicit_session_requested = (
+        isinstance(session_id, str) and bool(session_id.strip())
+    )
...
-        except requests.HTTPError as exc:
+        except requests.HTTPError as exc:
             project.last_session_id = None
             db.session.commit()
-            if had_candidate:
+            if explicit_session_requested:
                 raise ValueError("Selected session could not be loaded") from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/helpers.py` around lines 634 - 651, The code currently
treats any candidate_session_id failure the same and raises a ValueError, which
breaks fallback when the candidate came from the cached project.last_session_id;
change the logic so only explicit caller-supplied candidates cause a hard
failure. Record whether the candidate was provided by the caller (e.g., rename
or set a flag like explicit_candidate = candidate_session_id_was_passed) before
you fall back to project.last_session_id, then in the except requests.HTTPError
block clear project.last_session_id and commit as now but only raise
ValueError("Selected session could not be loaded") from exc when
explicit_candidate is true; otherwise swallow the error and allow fallback
session resolution to continue (do not raise).
backend/app/routes/helpers.py-890-891 (1)

890-891: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove permissive substring matching in session-event filter.

Line 890 uses session_id in payload, which can match unrelated IDs by substring and attach events to the wrong session.

Proposed fix
-    if session_id in payload:
-        return True
-
     try:
         parsed = json.loads(payload)
     except json.JSONDecodeError:
         return True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/helpers.py` around lines 890 - 891, The current check uses
permissive substring matching ("session_id in payload") which can incorrectly
match unrelated IDs; change it to a strict equality check against the structured
session field instead. Replace the substring test with something like
payload.get("session_id") == session_id (or, if payload is a stringified JSON,
parse it first and then compare the parsed_payload.get("session_id") ==
session_id) so only exact session ID matches attach the event; update the check
where the session-event filter is implemented (the code referencing session_id
and payload).
backend/app/routes/helpers.py-985-987 (1)

985-987: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize numeric parsing before assignment to avoid 500s.

maxRuns and runTimeoutMinutes are converted at assignment time (Line 985/986), outside the guarded parse block. Invalid values raise unhandled ValueError and return 500 instead of a validation error.

Proposed fix
     try:
         interval_minutes = int(body.get("intervalMinutes") or 15)
-        max_runs = body.get("maxRuns")
-        run_timeout_minutes = body.get("runTimeoutMinutes")
+        raw_max_runs = body.get("maxRuns")
+        raw_run_timeout_minutes = body.get("runTimeoutMinutes")
+        max_runs = int(raw_max_runs) if raw_max_runs not in (None, "") else None
+        run_timeout_minutes = (
+            int(raw_run_timeout_minutes)
+            if raw_run_timeout_minutes not in (None, "")
+            else None
+        )
         retry_count = int(body.get("retryCount") or 0)
         retry_backoff_minutes = int(body.get("retryBackoffMinutes") or 5)
-    except (TypeError, ValueError):
-        raise ValueError("Numeric task fields are invalid")
+    except (TypeError, ValueError) as exc:
+        raise ValueError("Numeric task fields are invalid") from exc
...
-    task.max_runs = int(max_runs) if max_runs not in (None, "") else None
-    task.run_timeout_minutes = int(run_timeout_minutes) if run_timeout_minutes not in (None, "") else None
+    task.max_runs = max_runs
+    task.run_timeout_minutes = run_timeout_minutes
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/helpers.py` around lines 985 - 987, The assignments to
task.max_runs and task.run_timeout_minutes currently parse ints inline and can
raise ValueError; move numeric parsing into the existing guarded/validated parse
block (where you validate other body fields) and only assign task.max_runs and
task.run_timeout_minutes after successful int conversion (e.g., parse max_runs
and run_timeout_minutes with try/except or the existing validator, convert empty
strings to None) so invalid inputs produce a validation error response instead
of an unhandled exception; keep the heartbeat line as-is (task.heartbeat_enabled
= bool(body.get("heartbeatEnabled", True))) and reference the variables
max_runs/run_timeout_minutes and the task properties
task.max_runs/task.run_timeout_minutes when locating code to change.
backend/app/routes/messages.py-87-117 (1)

87-117: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle invalid sessionId as a 400 before marking the project as failed.

_resolve_project_session(..., create_if_missing=False) is resolving caller input here. If it raises ValueError, the generic handler turns that into a 502 and Lines 115/224 set project.session_status = "error", even though this was just a bad request. Catch ValueError separately and leave the existing status alone.

Suggested fix
-    except Exception as exc:
+    except ValueError as exc:
+        return jsonify({"error": str(exc)}), 400
+    except Exception as exc:
         project.session_status = "error"
         db.session.commit()
         return jsonify({"error": f"Failed to send message: {exc}"}), 502

Apply the same split in run_project_command().

Also applies to: 146-226

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

In `@backend/app/routes/messages.py` around lines 87 - 117, The try/except around
sending messages treats caller-supplied bad session IDs (raised as ValueError by
_resolve_project_session) as server errors and marks
project.session_status="error"; update the handler to catch ValueError
separately: when _resolve_project_session(...) raises ValueError, do not change
project.session_status or commit a failure—return a 400 JSON response with the
validation message; keep the existing broad except Exception block to set
project.session_status="error", commit, and return 502 for real server failures.
Apply the identical ValueError-vs-Exception split to run_project_command() as
well so invalid sessionId inputs return 400 without mutating
project.session_status.
backend/app/routes/sessions.py-272-292 (1)

272-292: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't turn an invalid sessionId into a 502 and an "error" project state.

This route accepts a client-supplied sessionId, but any ValueError from _resolve_project_session(..., create_if_missing=False) is swallowed by the generic handler on Line 289. That returns 502 and marks the project as failed for a bad request. Handle ValueError like the compact/summarize routes do and leave the current status unchanged.

Suggested fix
-    except Exception as exc:
+    except ValueError as exc:
+        return jsonify({"error": str(exc)}), 400
+    except Exception as exc:
         project.session_status = "error"
         db.session.commit()
         return jsonify({"error": f"Failed to abort session: {exc}"}), 502
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/sessions.py` around lines 272 - 292, The handler currently
wraps all exceptions and sets project.session_status="error", but a ValueError
raised by _resolve_project_session(..., create_if_missing=False) should be
treated as a client error; update the try/except to catch ValueError separately
(before the broad Exception) and return a 400 response with the message (without
changing project.session_status), while keeping the existing broad Exception
handler to set project.session_status="error" and return 502 for real server
failures; make sure to reference the same session resolution flow
(_resolve_project_session, _ensure_project_session) and the abort action
(opencode_client.abort_session) so the new ValueError branch only handles
invalid sessionId cases.
backend/app/routes/messages.py-159-169 (1)

159-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the default provider/model pair correctly before calling summarize_session().

provider_data["default"] is used elsewhere in this PR as {provider_id: model_id}. Here Line 161 takes the first value and splits it on /, so provider_id becomes the model ID and model_id becomes empty. That breaks the /compact//summarize path whenever defaults are configured.

Suggested fix
-                default_model = default_map.get(list(default_map.keys())[0]) if default_map else None
-                if default_model and isinstance(default_model, str):
-                    provider_id, _, model_id = default_model.partition("/")
+                provider_id = next(iter(default_map), None)
+                model_id = (
+                    str(default_map.get(provider_id)).strip()
+                    if provider_id and default_map.get(provider_id)
+                    else ""
+                )
+                if provider_id and model_id:
                     opencode_client.summarize_session(
                         session_id,
                         provider_id=provider_id,
                         model_id=model_id,
                         directory=project.path,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/messages.py` around lines 159 - 169, The code incorrectly
extracts provider/model by taking the first value from default_map and
partitioning it, which flips provider_id and model_id; instead, pull the first
key as provider_id and use default_map[provider_id] as model_id (verify it's a
string), then pass those to opencode_client.summarize_session with session_id
and directory=project.path; update the logic around provider_data, default_map,
default_model and the call to summarize_session accordingly so provider_id is
the map key and model_id is the corresponding value.
backend/app/routes/sessions.py-343-352 (1)

343-352: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the default summarize model from default_map by key/value, not by splitting the value.

provider_data["default"] is treated elsewhere in this PR as {provider_id: model_id}. Here Lines 348-350 assume the first value looks like "provider/model", so provider_id ends up holding the model ID and model_id becomes empty. Default summarize requests will fail once defaults are configured.

Suggested fix
-        if not provider_id or not model_id:
-            default_model = default_map.get(list(default_map.keys())[0]) if default_map else None
-            if default_model and isinstance(default_model, str):
-                provider_id, _, model_id = default_model.partition("/")
+        if not provider_id or not model_id:
+            provider_id = next(iter(default_map), None)
+            model_id = (
+                str(default_map.get(provider_id)).strip()
+                if provider_id and default_map.get(provider_id)
+                else ""
+            )
+            if provider_id and model_id:
                 provider_id, _, model_id = default_model.partition("/")
             else:
                 return jsonify({"error": "providerID and modelID are required"}), 400
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/sessions.py` around lines 343 - 352, The code incorrectly
parses the default provider/model by splitting a string from default_map;
instead read the first key/value pair from default_map (which is structured as
{provider_id: model_id}) and assign provider_id to the key and model_id to the
value. In the block using provider_data/default_map (and variables provider_id,
model_id, default_model), replace the logic that expects "provider/model" with
logic that extracts the first item from default_map.items(), validates the types
(key is str for provider_id and value is str for model_id), and uses those
values as the defaults before returning the 400 error.
backend/app/routes/tasks.py-173-174 (1)

173-174: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid returning raw exception details in client-facing errors.

These responses expose internal exception text directly. Return stable/generic error messages and keep details in server logs.

Suggested fix
-    except Exception as exc:
-        return jsonify({"error": f"Failed to run task: {exc}"}), 502
+    except Exception:
+        return jsonify({"error": "Failed to run task"}), 502
...
-    except Exception as exc:
-        return jsonify({"error": str(exc)}), 400
+    except Exception:
+        return jsonify({"error": "Invalid schedule payload"}), 400

Also applies to: 262-263

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

In `@backend/app/routes/tasks.py` around lines 173 - 174, Replace the
client-facing error that returns raw exception text in the except blocks in
backend/app/routes/tasks.py (the "except Exception as exc:" handlers around the
task-running code and the similar handler at lines ~262-263) with a stable,
generic JSON error message (e.g., {"error":"Failed to run task"}) and log the
full exception server-side using a logger method such as logger.exception or
current_app.logger.exception so the stacktrace is retained for debugging without
exposing internals to clients; update both occurrences (the handler that
currently does return jsonify({"error": f"Failed to run task: {exc}"}), 502) to
follow this pattern.
backend/app/git_routes.py-596-597 (1)

596-597: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return a stable 500 error message instead of raw exception text.

Returning str(e) may leak internal command/runtime details to clients.

Suggested fix
-    except Exception as e:
-        return jsonify({"error": str(e)}), 500
+    except Exception:
+        return jsonify({"error": "Failed to generate git diff"}), 500
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/git_routes.py` around lines 596 - 597, The except block in
backend/app/git_routes.py currently returns raw exception text via
jsonify({"error": str(e)}), which can leak internals; change this to return a
stable 500 message such as jsonify({"error":"Internal server error"}) with a 500
status, and instead log the original exception internally (e.g., use
logger.exception or current_app.logger.exception referencing the caught variable
e) so the details are preserved in server logs but not sent to clients; update
the except block where jsonify and the exception variable e are used to
implement this.
backend/app/git_routes.py-578-591 (1)

578-591: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bound untracked patch generation to avoid large/binary payload blowups.

Reading and returning full content for every untracked file is unbounded and can create very large responses (memory/latency/UI impact).

Suggested fix
         for u in repo.untracked_files:
             try:
                 from pathlib import Path
-                content = Path(repo.working_dir, u).read_text(errors="replace")
+                path = Path(repo.working_dir, u)
+                max_bytes = 200_000
+                if path.stat().st_size > max_bytes:
+                    entries.append({
+                        "path": u,
+                        "changeType": "?",
+                        "patch": f"[untracked file omitted: larger than {max_bytes} bytes]",
+                    })
+                    continue
+                raw = path.read_bytes()
+                if b"\x00" in raw[:8192]:
+                    entries.append({
+                        "path": u,
+                        "changeType": "?",
+                        "patch": "[binary file omitted]",
+                    })
+                    continue
+                content = raw.decode("utf-8", errors="replace")
                 lines = content.splitlines()
                 line_count = len(lines)
                 patch_lines = ["--- /dev/null", f"+++ b/{u}", f"@@ -0,0 +1,{line_count} @@"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/git_routes.py` around lines 578 - 591, The current loop over
repo.untracked_files in git_routes.py reads entire file contents
(Path(...).read_text) which can return arbitrarily large or binary data; change
this to read only a bounded amount and detect binary content: open the file in
binary, read up to a max byte limit (e.g., 64KB), if a null byte or non-text
bytes are present mark changeType as "?" with a note like "(binary or
truncated)" and do not attempt to split into full lines, otherwise decode the
bytes with errors="replace", split into lines limited to a max line count, build
patch_lines from the truncated/decoded content and reflect the truncated
line_count in the patch header and/or append a truncation marker so entries
(path, changeType, patch) never contain unbounded payloads; update the code
around the repo.untracked_files loop and the entries/potch_lines construction to
implement these checks and limits.
backend/app/git_routes.py-552-576 (1)

552-576: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle diff failures per file so one bad path doesn’t drop the whole section.

Current try scope wraps the full staged/changed blocks. A single GitCommandError skips all remaining files in that section.

Suggested fix
-            try:
-                staged_output = repo.git.diff("--cached", unified=5)
-                if staged_output.strip():
-                    staged_paths = _staged_paths(repo)
-                    for p in staged_paths:
-                        patch = repo.git.diff("--cached", "--", p, unified=5)
-                        entries.append({
-                            "path": p,
-                            "changeType": "M",
-                            "patch": patch,
-                        })
-            except exc.GitCommandError:
-                pass
+            staged_paths = _staged_paths(repo)
+            for p in staged_paths:
+                try:
+                    patch = repo.git.diff("--cached", "--", p, unified=5)
+                except exc.GitCommandError:
+                    continue
+                entries.append({
+                    "path": p,
+                    "changeType": "M",
+                    "patch": patch,
+                })

-            try:
-                changed_paths = _changed_paths(repo)
-                for p in changed_paths:
-                    patch = repo.git.diff("--", p, unified=5)
-                    entries.append({
-                        "path": p,
-                        "changeType": "M",
-                        "patch": patch,
-                    })
-            except exc.GitCommandError:
-                pass
+            changed_paths = _changed_paths(repo)
+            for p in changed_paths:
+                try:
+                    patch = repo.git.diff("--", p, unified=5)
+                except exc.GitCommandError:
+                    continue
+                entries.append({
+                    "path": p,
+                    "changeType": "M",
+                    "patch": patch,
+                })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/git_routes.py` around lines 552 - 576, The current try/except
around the whole staged and changed blocks causes one GitCommandError to skip
all files; modify the code to catch errors per file by keeping the initial
existence checks (repo.git.diff("--cached", unified=5) and _changed_paths(repo))
but move the try/except inside the loops so each call to
repo.git.diff("--cached", "--", p, unified=5) and repo.git.diff("--", p,
unified=5) is wrapped in its own try/except exc.GitCommandError that logs or
silently continues and does not abort the rest of the loop; update the sections
that reference _staged_paths, _changed_paths, repo.git.diff and entries so each
file is handled independently.
backend/app/routes/tasks.py-96-104 (1)

96-104: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t upsert-create when a taskId is explicitly provided but not found.

If the client sends taskId and it misses, this currently creates a new task instead of failing. That can create unintended duplicates and masks client-side state bugs.

Suggested fix
     try:
         body = request.get_json(silent=True) or {}
         task_id = str(body.get("id") or body.get("taskId") or "").strip()
-        task = ScheduledTask.query.filter_by(id=int(task_id), project_id=project_id).first() if task_id else None
+        task = ScheduledTask.query.filter_by(id=int(task_id), project_id=project_id).first() if task_id else None
+        if task_id and task is None:
+            return jsonify({"error": "Scheduled task not found"}), 404
     except (TypeError, ValueError):
         return jsonify({"error": "Invalid task id"}), 400
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/tasks.py` around lines 96 - 104, When a client provides a
taskId but no matching ScheduledTask exists, do not create (upsert) a new task;
instead return a 404 error. Modify the logic around task_id and task in the
handler so that after computing task_id and querying ScheduledTask (variables:
body, task_id, task, ScheduledTask, project_id), you check: if task_id is
non-empty and task is None then return jsonify({"error": "Task not found"}),
404; only create and db.session.add a new ScheduledTask when task_id was not
provided (i.e., task_id is empty and task is None).
frontend/src/utils/streamUtils.ts-83-86 (1)

83-86: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not treat every string response as a resolved permission.

At Line 84–86, "pending" (or any arbitrary string) is treated as resolved, which can prematurely clear approval requests.

🔧 Proposed fix
     const response = record.response ?? record.decision ?? record.action;
     if (typeof response === "string") {
-      return true;
+      const normalized = response.toLowerCase();
+      return (
+        normalized.includes("allow") ||
+        normalized.includes("approve") ||
+        normalized.includes("deny") ||
+        normalized.includes("reject")
+      );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/streamUtils.ts` around lines 83 - 86, The current check
treats any string as a resolved permission (const response = record.response ??
record.decision ?? record.action; if (typeof response === "string") return
true;), which will mark values like "pending" as resolved; change this to only
accept explicit success tokens by replacing the typeof check with a whitelist
comparison (e.g., response === "approved" || response === "granted" || response
=== "allowed") or a Set lookup of allowed success strings, so only those known
resolved values cause the function (or helper that uses response) to return true
and strings like "pending" remain unresolved.
frontend/src/utils/streamUtils.ts-274-279 (1)

274-279: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fallback classification misses message.part.updated as a part update.

When only wrapper.type is present, message.part.updated is currently classified as a generic message update (Line 274–279) instead of hasPartUpdate, which can skip part-specific handlers.

🔧 Proposed fix
       } else if (
+        directType === "message.part.updated"
+      ) {
+        result.hasPartUpdate = true;
+      } else if (
         directType.startsWith("text.") ||
         directType.startsWith("tool.") ||
         directType.startsWith("message.") ||
         directType === "prompted"
       ) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/streamUtils.ts` around lines 274 - 279, The fallback
classification currently treats wrapper.type values like "message.part.updated"
as a generic message update and sets result.hasMessageUpdate; update the
classification logic that inspects directType/wrapper.type so that when
directType === "message.part.updated" (or wrapper.type resolves to that string)
you set result.hasPartUpdate = true (and avoid only setting
result.hasMessageUpdate), ensuring downstream part-specific handlers run; locate
the branch that checks directType.startsWith(...) and directType === "prompted"
and add an explicit check for "message.part.updated" before the generic message
update assignment.
frontend/src/utils/projectUtils.ts-15-18 (1)

15-18: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize slash-only roots to / instead of empty string.

At Line 18, inputs like "//" normalize to "", which turns an absolute root into a relative path in later path-building flows.

🔧 Proposed fix
 export function normalizeProjectRootPath(path: string) {
   const trimmed = path.trim();
-  if (!trimmed || trimmed === "/") {
+  if (!trimmed) {
     return trimmed;
   }
+  if (/^\/+$/.test(trimmed)) {
+    return "/";
+  }
   return trimmed.replace(/\/+$/, "");
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/projectUtils.ts` around lines 15 - 18, The current
normalization returns an empty string for inputs that are only slashes (e.g.,
"//") because trimmed.replace(/\/+$/, "") yields "", so update the logic that
handles the variable trimmed: if trimmed is falsy or consists only of one or
more slashes, return "/" instead of trimmed; specifically detect when
trimmed.replace(/\/+$/, "") is empty and return "/" (preserving the existing
behavior for other paths), adjusting the branch around the trimmed checks in the
path-normalization function that contains the shown trimmed logic.
frontend/src/components/projects/ProjectFilesPanel.tsx-218-224 (1)

218-224: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent duplicate directory expansion requests on ArrowRight.

Line 223 can fire repeatedly while a directory is already loading, causing redundant API calls and racey UI state.

Proposed fix
-      } else if (!loadedDirectories.includes(focusedEntry.entry.path)) {
+      } else if (
+        !loadedDirectories.includes(focusedEntry.entry.path) &&
+        !loadingDirectories.includes(focusedEntry.entry.path)
+      ) {
         void onExpandDirectory(focusedEntry.entry.path);
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/projects/ProjectFilesPanel.tsx` around lines 218 -
224, The ArrowRight handler can call onExpandDirectory repeatedly while a
directory is already loading; add a loading guard to prevent duplicate requests
by tracking a set (e.g., loadingDirectories) of paths currently being expanded:
check loadingDirectories.has(focusedEntry.entry.path) alongside
collapsedDirectories and loadedDirectories before calling onExpandDirectory, add
the path to loadingDirectories immediately before invoking the async expand
(void onExpandDirectory(...)) and remove it from loadingDirectories in the
expand completion/error handling; update the handler and the async expand logic
(references: focusedEntry, collapsedDirectories, loadedDirectories,
toggleDirectory, onExpandDirectory) to use this loading set so only one
expansion request can be in-flight per directory.
frontend/src/components/toolbar/RuntimeControls.tsx-120-135 (1)

120-135: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always expose “+ New session” in the session picker.

When there are no sessions, the selector renders only a disabled placeholder, so users can’t create a first/new session from this control.

Suggested fix
         <select
           value={activeSessionId ?? ""}
           onChange={(event) => handleSessionChange(event.currentTarget.value)}
           disabled={sessionLoading || sessionSwitching}
         >
-          {sortedSessions.length === 0 ? (
-            <option value="" disabled>{sessionLoading ? "Loading..." : "No session"}</option>
-          ) : (
-            <>
-              {sortedSessions.map((session) => {
-                const label = session.title || "Untitled session";
-                const ts = formatSessionTimestamp(session.updatedAt ?? session.createdAt);
-                return (
-                  <option key={session.id} value={session.id}>
-                    {label} — {ts}
-                  </option>
-                );
-              })}
-              <option value="__new__">+ New session</option>
-            </>
-          )}
+          <option value="" disabled>
+            {sessionLoading ? "Loading..." : "Select session"}
+          </option>
+          {sortedSessions.map((session) => {
+            const label = session.title || "Untitled session";
+            const ts = formatSessionTimestamp(session.updatedAt ?? session.createdAt);
+            return (
+              <option key={session.id} value={session.id}>
+                {label} — {ts}
+              </option>
+            );
+          })}
+          <option value="__new__">+ New session</option>
         </select>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/toolbar/RuntimeControls.tsx` around lines 120 - 135,
The session picker currently hides the "+ New session" option when
sortedSessions.length === 0, preventing creation of the first session; change
the JSX in RuntimeControls so the <option value="__new__">+ New session</option>
is always rendered (either move it outside the sortedSessions conditional or add
it alongside the disabled placeholder), keeping the disabled placeholder logic
for the empty state and preserving existing values/keys (use the existing
"__new__" value and retain formatSessionTimestamp and sessionLoading checks).
frontend/src/components/projects/VirtualizedProjectList.tsx-97-109 (1)

97-109: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent duplicate onReachEnd() calls near the bottom.

The current threshold check can fire onReachEnd() repeatedly across rapid scroll events before isLoadingMore flips, which can trigger duplicate pagination requests.

Suggested fix
 import React, { useEffect, useRef, useState } from "react";
@@
   const containerRef = useRef<HTMLDivElement | null>(null);
+  const reachEndRequestedRef = useRef(false);
@@
+  useEffect(() => {
+    if (!isLoadingMore) {
+      reachEndRequestedRef.current = false;
+    }
+  }, [isLoadingMore]);
+
   return (
@@
-        if (visibleBottom >= event.currentTarget.scrollHeight - threshold) {
+        if (
+          visibleBottom >= event.currentTarget.scrollHeight - threshold &&
+          !reachEndRequestedRef.current
+        ) {
+          reachEndRequestedRef.current = true;
           onReachEnd();
         }
       }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/projects/VirtualizedProjectList.tsx` around lines 97
- 109, The onScroll handler can call onReachEnd multiple times before
isLoadingMore flips; add a local reentrancy guard (e.g., a useRef like
isFetchingRef) and check it alongside hasMore/isLoadingMore inside the onScroll
callback, set isFetchingRef.current = true immediately before invoking
onReachEnd, and clear it when loading finishes (watch isLoadingMore with an
effect and set isFetchingRef.current = false when isLoadingMore becomes false);
this uses the existing symbols onScroll, setScrollTop, hasMore, isLoadingMore,
onReachEnd, threshold and rowHeight to prevent duplicate pagination requests.
🟡 Minor comments (5)
frontend/src/components/auth/LoginView.tsx-24-33 (1)

24-33: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prevent empty-password submissions client-side.

At Line 31, submit is disabled only during loading. Empty/whitespace passwords still submit and trigger unnecessary auth attempts.

🔧 Proposed fix
         <input
           type="password"
           value={password}
           onChange={(event) => setPassword(event.target.value)}
           placeholder="Enter password"
           autoComplete="current-password"
+          required
         />
-        <button disabled={loading} type="submit">
+        <button disabled={loading || !password.trim()} type="submit">
           {loading ? "Signing in..." : "Sign in"}
         </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/auth/LoginView.tsx` around lines 24 - 33, The submit
button currently only disables during loading, allowing empty/whitespace
passwords to be submitted; update the client-side check to prevent this by
disabling the button when password.trim() === '' in addition to loading (i.e.,
change the button's disabled prop to loading || password.trim() === '') and/or
add a guard at the start of the submit handler (e.g., handleSubmit) that returns
early if password.trim() is empty to avoid unnecessary auth attempts.
frontend/src/utils/streamUtils.ts-315-317 (1)

315-317: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Support both camelCase and uppercase ID keys in part payload parsing.

At Line 315–317, only sessionID/messageID are read. If upstream emits sessionId/messageId, the part event is dropped as incomplete.

🔧 Proposed fix
-    const sessionID = typeof parsed.sessionID === "string" ? parsed.sessionID : null;
-    const messageID = typeof part.messageID === "string" ? part.messageID : null;
+    const sessionID =
+      typeof parsed.sessionID === "string"
+        ? parsed.sessionID
+        : typeof parsed.sessionId === "string"
+          ? parsed.sessionId
+          : null;
+    const messageID =
+      typeof part.messageID === "string"
+        ? part.messageID
+        : typeof part.messageId === "string"
+          ? part.messageId
+          : null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/streamUtils.ts` around lines 315 - 317, The parsing
currently only accepts parsed.sessionID, part.messageID and part.id which drops
events when upstream uses sessionId/messageId/ID variants; update the
assignments for sessionID, messageID and partID to check both variants (e.g.,
sessionID OR sessionId, messageID OR messageId, part.id OR part.ID) and fall
back to null if neither is a string so the rest of the logic in streamUtils.ts
that uses parsed and part will accept both naming conventions.
frontend/src/components/chat/MessagePartCard.tsx-124-126 (1)

124-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix step-finish placeholder conditions to match displayed fields.

Line 167 uses falsy checks (!cost, !tokens) that don’t match the actual rendering predicates, so cost = 0 and tokens = {} can produce incorrect placeholder behavior.

Proposed fix
   const tokens = part.tokens as Record<string, unknown> | undefined;
   const cost = typeof part.cost === "number" ? part.cost : null;
+  const hasCost = cost !== null;
+  const hasTokens = !!tokens && Object.keys(tokens).length > 0;
@@
-          {cost !== null && (
+          {hasCost && (
             <div className="step-info">
               <span className="step-label">Cost:</span>
               <code className="step-value">${cost.toFixed(4)}</code>
             </div>
           )}
-          {tokens && Object.keys(tokens).length > 0 && (
+          {hasTokens && (
             <div className="step-info">
               <span className="step-label">Tokens:</span>
               <div className="step-token-grid">
                 {Object.entries(tokens).map(([key, val]) => (
@@
-      {partType === "step-finish" && !reason && !cost && !tokens && (
+      {partType === "step-finish" && !reason && !hasCost && !hasTokens && (
         <p className="step-placeholder">Step finished</p>
       )}

Also applies to: 143-169

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

In `@frontend/src/components/chat/MessagePartCard.tsx` around lines 124 - 126, The
placeholder logic uses falsy checks (!cost, !tokens) which treat cost=0 and
tokens={} incorrectly; update the conditions in MessagePartCard to check for
explicit null/undefined instead (e.g., use cost === null or cost === undefined /
cost == null, and tokens == null) or, for tokens, check emptiness with
Object.keys(tokens).length === 0 if empty object should be treated as
placeholder; update all occurrences in the 143-169 block (references: part,
tokens, cost, and the rendering branches that show placeholders) to use these
explicit null/undefined or emptiness checks so displayed fields match the
placeholder behavior.
frontend/src/components/projects/ProjectFilesPanel.tsx-143-143 (1)

143-143: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid contradictory empty-state messaging when a filter is active.

The current checks can show both “No files match this filter.” and “No files available in this project yet.” for the same filtered state.

Proposed fix
-  const fileEntries = visibleEntries.filter((entry) => !entry.isDir);
+  const fileEntries = visibleEntries.filter((entry) => !entry.isDir);
+  const totalFileEntries = dedupedEntries.filter((entry) => !entry.isDir);
@@
-          {!loading && !loadError && fileEntries.length === 0 ? (
+          {!loading && !loadError && !normalizedQuery && totalFileEntries.length === 0 ? (
             <div className="project-files-muted">No files available in this project yet.</div>
           ) : null}

Also applies to: 378-380, 493-495

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

In `@frontend/src/components/projects/ProjectFilesPanel.tsx` at line 143, The
empty-state logic currently derives fileEntries from visibleEntries and can
render both “No files match this filter.” and “No files available in this
project yet.” simultaneously; update the conditional rendering in
ProjectFilesPanel (where fileEntries is computed and where empty states are
rendered) to first check whether any filter/search/category is active (e.g.,
using the same predicates that produce visibleEntries or an explicit boolean
like isFilterActive/searchQuery/selectedCategory) and if true render only the
filtered-message (“No files match this filter.”), otherwise render the
project-empty message (“No files available in this project yet.”); apply the
same fix to the other two empty-state blocks referenced (around the other
occurrences) so only one empty-state message shows depending solely on whether
filters are active.
frontend/src/components/ui/QuestionCard.tsx-38-56 (1)

38-56: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add defensive check for info.options to prevent runtime error.

If info.options is undefined or null, calling .map() will throw a TypeError. Apply the same defensive pattern used for question.questions on line 29.

🛡️ Proposed fix
               <div className="question-options">
-                {info.options.map((option, optionIndex) => {
+                {(info.options ?? []).map((option, optionIndex) => {
                   const letter = String.fromCharCode(65 + optionIndex);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/QuestionCard.tsx` around lines 38 - 56, The
component currently assumes info.options exists and calls info.options.map which
can throw if options is null/undefined; update the rendering in QuestionCard
(the block that iterates info.options) to guard the iteration the same way
question.questions is protected — e.g., only map when info.options is truthy or
fallback to an empty array — so the logic around info.options, selectedValues,
onToggleOption, questionIndex and question.id remains unchanged but safe when
info.options is missing.
🧹 Nitpick comments (1)
frontend/src/utils/messageUtils.ts (1)

368-390: ⚡ Quick win

Inconsistent type handling in asBoolean—consider matching the defensive pattern of asString and asNumber.

asBoolean should explicitly type-check for booleans before falling back to a default, consistent with the other coercers. While the actual payload yields proper JSON-deserialized booleans (not strings), defensive typing improves code clarity.

Suggested improvement
-  const asBoolean = (value: unknown) => Boolean(value);
+  const asBoolean = (value: unknown) => typeof value === "boolean" ? value : false;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/utils/messageUtils.ts` around lines 368 - 390, asBoolean
currently coerces with Boolean(value) which treats truthy non-boolean values
(like strings) as true; change it to mirror asNumber/asString defensive pattern
by returning the raw boolean only when typeof value === "boolean" and otherwise
returning null (so asBoolean = (value: unknown) => (typeof value === "boolean" ?
value : null)); update any downstream usages (e.g., heartbeatLoaded,
goalAttempted) that expect a nullable boolean to rely on this stricter coerce
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31e1ae57-0717-4e90-aa2f-0af204e031dd

📥 Commits

Reviewing files that changed from the base of the PR and between 5060804 and f00171c.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (57)
  • .env.example
  • PDR.md
  • README.md
  • backend/app/config.py
  • backend/app/git_routes.py
  • backend/app/opencode.py
  • backend/app/routes.py
  • backend/app/routes/__init__.py
  • backend/app/routes/files.py
  • backend/app/routes/helpers.py
  • backend/app/routes/messages.py
  • backend/app/routes/notifications.py
  • backend/app/routes/opencode.py
  • backend/app/routes/projects.py
  • backend/app/routes/runtime.py
  • backend/app/routes/scheduler_routes.py
  • backend/app/routes/sessions.py
  • backend/app/routes/stt.py
  • backend/app/routes/tasks.py
  • docs/task-feature-test-plan.md
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/api.ts
  • frontend/src/components/auth/LoginView.tsx
  • frontend/src/components/chat/AgentActivityCard.tsx
  • frontend/src/components/chat/ChatStateCard.tsx
  • frontend/src/components/chat/ChatTransitionStrip.tsx
  • frontend/src/components/chat/DiffPanel.tsx
  • frontend/src/components/chat/EmptyState.tsx
  • frontend/src/components/chat/MessageBubble.tsx
  • frontend/src/components/chat/MessagePartCard.tsx
  • frontend/src/components/chat/MessageParts.tsx
  • frontend/src/components/chat/RichMessageText.tsx
  • frontend/src/components/chat/TaskRunTimelineRow.tsx
  • frontend/src/components/projects/ProjectFilesPanel.tsx
  • frontend/src/components/projects/ProjectItem.tsx
  • frontend/src/components/projects/VirtualizedProjectList.tsx
  • frontend/src/components/tasks/RalphLoopPanel.tsx
  • frontend/src/components/tasks/ScheduledTaskPanel.tsx
  • frontend/src/components/toolbar/InstallControls.tsx
  • frontend/src/components/toolbar/NotificationControls.tsx
  • frontend/src/components/toolbar/RuntimeControls.tsx
  • frontend/src/components/ui/CommandPickerModal.tsx
  • frontend/src/components/ui/FixtureBanner.tsx
  • frontend/src/components/ui/QuestionCard.tsx
  • frontend/src/styles.css
  • frontend/src/types.ts
  • frontend/src/types/internal.ts
  • frontend/src/utils/fileUtils.ts
  • frontend/src/utils/formatting.ts
  • frontend/src/utils/messageUtils.ts
  • frontend/src/utils/miscUtils.ts
  • frontend/src/utils/projectUtils.ts
  • frontend/src/utils/streamUtils.ts
  • frontend/src/utils/taskUtils.ts
  • tasks.md
  • ui-spec.md
💤 Files with no reviewable changes (4)
  • PDR.md
  • docs/task-feature-test-plan.md
  • tasks.md
  • ui-spec.md
📜 Review details
🧰 Additional context used
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 14-14: [UnorderedKey] The NOTIFICATION_NTFY_TOPIC_URL key should go before the OPENCODE_CORS_ORIGINS key

(UnorderedKey)

🪛 OpenGrep (1.21.0)
frontend/src/utils/messageUtils.ts

[ERROR] 284-284: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🪛 Ruff (0.15.13)
backend/app/git_routes.py

[warning] 596-596: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/__init__.py

[warning] 41-41: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

backend/app/routes/notifications.py

[warning] 69-69: Do not catch blind exception: Exception

(BLE001)


[warning] 92-92: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/opencode.py

[warning] 33-33: Do not catch blind exception: Exception

(BLE001)


[warning] 65-65: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/files.py

[warning] 47-47: Do not catch blind exception: Exception

(BLE001)


[warning] 78-78: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/projects.py

[warning] 72-72: Do not catch blind exception: Exception

(BLE001)


[warning] 204-204: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/runtime.py

[warning] 71-71: Do not catch blind exception: Exception

(BLE001)


[warning] 158-158: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/stt.py

[warning] 127-127: Do not catch blind exception: Exception

(BLE001)


[warning] 175-175: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/tasks.py

[warning] 173-173: Do not catch blind exception: Exception

(BLE001)


[warning] 262-262: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/messages.py

[warning] 60-60: Do not catch blind exception: Exception

(BLE001)


[warning] 114-114: Do not catch blind exception: Exception

(BLE001)


[warning] 161-161: Prefer next(iter(default_map.keys())) over single element slice

Replace with next(iter(default_map.keys()))

(RUF015)


[warning] 223-223: Do not catch blind exception: Exception

(BLE001)


[warning] 244-244: Do not catch blind exception: Exception

(BLE001)


[warning] 264-264: Do not catch blind exception: Exception

(BLE001)


[warning] 288-288: Do not catch blind exception: Exception

(BLE001)


[warning] 298-298: Do not catch blind exception: Exception

(BLE001)


[warning] 313-313: Do not catch blind exception: Exception

(BLE001)


[warning] 359-359: Do not catch blind exception: Exception

(BLE001)


[warning] 405-405: Do not catch blind exception: Exception

(BLE001)


[warning] 476-476: Do not catch blind exception: Exception

(BLE001)


[warning] 517-517: Do not catch blind exception: Exception

(BLE001)


[warning] 544-544: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/sessions.py

[warning] 40-40: Do not catch blind exception: Exception

(BLE001)


[warning] 89-89: Do not catch blind exception: Exception

(BLE001)


[warning] 131-131: Do not catch blind exception: Exception

(BLE001)


[warning] 222-222: Do not catch blind exception: Exception

(BLE001)


[warning] 259-259: Do not catch blind exception: Exception

(BLE001)


[warning] 289-289: Do not catch blind exception: Exception

(BLE001)


[warning] 319-319: Do not catch blind exception: Exception

(BLE001)


[warning] 348-348: Prefer next(iter(default_map.keys())) over single element slice

Replace with next(iter(default_map.keys()))

(RUF015)


[warning] 365-365: Do not catch blind exception: Exception

(BLE001)

backend/app/routes/helpers.py

[warning] 12-66: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)


[warning] 198-198: Do not catch blind exception: Exception

(BLE001)


[warning] 396-396: Do not catch blind exception: Exception

(BLE001)


[warning] 440-440: Do not catch blind exception: Exception

(BLE001)


[warning] 943-943: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (35)
.env.example (1)

14-14: LGTM!

README.md (1)

5-506: LGTM!

backend/app/config.py (1)

34-35: LGTM!

Also applies to: 70-70, 123-124

backend/app/opencode.py (1)

229-307: LGTM!

backend/app/routes.py (1)

1-25: LGTM!

backend/app/routes/__init__.py (1)

1-42: LGTM!

frontend/package.json (1)

13-15: LGTM!

frontend/src/types.ts (1)

3-3: LGTM!

Also applies to: 38-38, 47-55, 78-78, 114-191

frontend/src/types/internal.ts (1)

1-109: LGTM!

frontend/src/api.ts (1)

5-5: LGTM!

Also applies to: 13-13, 73-78, 141-148, 151-155, 161-162, 169-170, 203-260, 328-338

frontend/src/utils/fileUtils.ts (1)

1-91: LGTM!

frontend/src/utils/formatting.ts (1)

1-174: LGTM!

frontend/src/utils/messageUtils.ts (1)

1-367: LGTM!

Also applies to: 391-397

frontend/src/utils/miscUtils.ts (1)

1-185: LGTM!

frontend/src/utils/taskUtils.ts (1)

1-157: LGTM!

frontend/src/components/chat/AgentActivityCard.tsx (1)

1-74: LGTM!

frontend/src/components/chat/ChatStateCard.tsx (1)

1-18: LGTM!

frontend/src/components/chat/ChatTransitionStrip.tsx (1)

1-18: LGTM!

frontend/src/components/chat/DiffPanel.tsx (1)

1-119: LGTM!

frontend/src/components/chat/EmptyState.tsx (1)

1-11: LGTM!

frontend/src/components/chat/MessageBubble.tsx (1)

1-77: LGTM!

frontend/src/components/chat/MessageParts.tsx (1)

1-78: LGTM!

frontend/src/components/chat/RichMessageText.tsx (1)

1-32: LGTM!

frontend/src/components/chat/TaskRunTimelineRow.tsx (1)

1-37: LGTM!

frontend/src/components/projects/ProjectItem.tsx (1)

1-47: LGTM!

frontend/src/components/projects/VirtualizedProjectList.tsx (1)

1-96: LGTM!

Also applies to: 112-146

frontend/src/components/tasks/RalphLoopPanel.tsx (1)

1-102: LGTM!

frontend/src/components/tasks/ScheduledTaskPanel.tsx (1)

1-455: LGTM!

frontend/src/components/toolbar/InstallControls.tsx (1)

1-38: LGTM!

frontend/src/components/toolbar/NotificationControls.tsx (1)

1-84: LGTM!

frontend/src/components/toolbar/RuntimeControls.tsx (1)

1-119: LGTM!

Also applies to: 136-161

frontend/src/components/ui/CommandPickerModal.tsx (1)

1-58: LGTM!

frontend/src/components/ui/FixtureBanner.tsx (1)

1-7: LGTM!

frontend/src/components/ui/QuestionCard.tsx (1)

1-37: LGTM!

Also applies to: 57-80

frontend/src/styles.css (1)

686-948: LGTM!

Also applies to: 1078-1192, 1759-1794, 2039-2535, 2943-2948, 3394-3527, 3598-3765, 3947-3952, 4477-4479, 4623-4636, 4744-4803

Comment on lines +44 to +47
ntfy_topic_url = str(body.get("ntfyTopicUrl") or "").strip()
_set_notification_settings(channel, ntfy_topic_url)
db.session.commit()
return jsonify({"ok": True, "channel": channel, "ntfyTopicUrl": ntfy_topic_url})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Do not pass caller-controlled notification URLs straight to the outbound sender.

ntfyTopicUrl is accepted from settings updates and per-request bodies, then sent directly to _send_ntfy_notification(). That makes these routes an SSRF primitive against arbitrary internal or cloud-metadata URLs for any authenticated caller. Restrict this to an allowlisted ntfy origin, or accept only a topic name and construct the URL server-side.

Also applies to: 59-67, 82-90

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

In `@backend/app/routes/notifications.py` around lines 44 - 47, The code currently
accepts caller-controlled ntfyTopicUrl (ntfy_topic_url) and persists/sends it
(via _set_notification_settings and later _send_ntfy_notification), which allows
SSRF; instead validate and normalize this input by either (A) accepting only a
simple topic name and constructing the full ntfy URL server-side, or (B)
enforcing an allowlist of permitted ntfy origins and rejecting any URL whose
origin is not on that list; apply the same change to all places that read
ntfyTopicUrl (including the other sections referenced around lines 59–67 and
82–90), and ensure callers receive a validation error when the value is
missing/invalid rather than forwarding it to _send_ntfy_notification.

Comment on lines +176 to +185
normalized_path = _normalize_project_path(path)

if not os.path.isdir(normalized_path):
try:
os.makedirs(normalized_path, exist_ok=True)
except OSError as exc:
return jsonify(
{"error": f"Unable to create project folder: {exc}"}
), 400

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Reject / as a valid project path here too.

sync_projects_from_opencode() already treats "/" as unsafe, but create_project() currently accepts it. A project rooted at / would let the rest of the project-scoped file/session APIs operate over the entire host filesystem instead of a single repo.

Suggested fix
     normalized_path = _normalize_project_path(path)
+    if normalized_path == "/":
+        return jsonify({"error": "Project path cannot be /"}), 400
 
     if not os.path.isdir(normalized_path):
         try:
             os.makedirs(normalized_path, exist_ok=True)
📝 Committable suggestion

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

Suggested change
normalized_path = _normalize_project_path(path)
if not os.path.isdir(normalized_path):
try:
os.makedirs(normalized_path, exist_ok=True)
except OSError as exc:
return jsonify(
{"error": f"Unable to create project folder: {exc}"}
), 400
normalized_path = _normalize_project_path(path)
if normalized_path == "/":
return jsonify({"error": "Project path cannot be /"}), 400
if not os.path.isdir(normalized_path):
try:
os.makedirs(normalized_path, exist_ok=True)
except OSError as exc:
return jsonify(
{"error": f"Unable to create project folder: {exc}"}
), 400
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/projects.py` around lines 176 - 185, Reject a root path
after normalization to prevent creating a project at filesystem root: after
calling _normalize_project_path(path) (and before trying os.path.isdir /
os.makedirs) add a check that normalized_path is not the system root (e.g.,
normalized_path == os.path.sep or normalized_path == "/") and if it is, return a
400 JSON error (same style as existing error responses). Update the
create_project route logic (the block using normalized_path and os.makedirs) so
it refuses "/" and any equivalent normalized root path before attempting
directory creation.

Comment thread frontend/src/api.ts
Comment on lines +680 to +682
export function apiGitDiff(projectId: string) {
return request<GitDiffResponse>(`/api/projects/${projectId}/git/diff`).then(r => r.diff);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify missing/used git diff types in api and definitions in shared types.
rg -n 'GitDiffResponse|GitDiffEntry|from "./types"' frontend/src/api.ts frontend/src/types.ts

Repository: Rishabh-Bajpai/mobile-opencode-control

Length of output: 447


Import GitDiffResponse to fix a compile-time type error.

apiGitDiff uses GitDiffResponse but it is not imported from ./types, causing TypeScript compilation to fail.

Proposed fix
 import type {
   ChatMessage,
   AppStateResponse,
   OpenCodeCommand,
   NotificationSettings,
   Project,
   ProjectSession,
   ProjectRuntimeOptions,
   ProjectSessionsResponse,
   ProjectFileContent,
   ProjectDirectoryListResponse,
   ProjectsResponse,
   QuestionRequest,
   ScheduledTaskDetails,
   ScheduledTaskMetrics,
   ScheduledTaskRun,
   ScheduledTask,
   ProjectsSyncResponse,
   SessionDiffEntry,
   GitDiffEntry,
+  GitDiffResponse,
   TimelineEvent,
   PrdData,
   PrdResponse,
 } from "./types";
📝 Committable suggestion

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

Suggested change
export function apiGitDiff(projectId: string) {
return request<GitDiffResponse>(`/api/projects/${projectId}/git/diff`).then(r => r.diff);
}
import type {
ChatMessage,
AppStateResponse,
OpenCodeCommand,
NotificationSettings,
Project,
ProjectSession,
ProjectRuntimeOptions,
ProjectSessionsResponse,
ProjectFileContent,
ProjectDirectoryListResponse,
ProjectsResponse,
QuestionRequest,
ScheduledTaskDetails,
ScheduledTaskMetrics,
ScheduledTaskRun,
ScheduledTask,
ProjectsSyncResponse,
SessionDiffEntry,
GitDiffEntry,
GitDiffResponse,
TimelineEvent,
PrdData,
PrdResponse,
} from "./types";
export function apiGitDiff(projectId: string) {
return request<GitDiffResponse>(`/api/projects/${projectId}/git/diff`).then(r => r.diff);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/api.ts` around lines 680 - 682, The TypeScript compile error is
caused by using the GitDiffResponse type in apiGitDiff without importing it;
open frontend/src/api.ts and add an import for GitDiffResponse from './types'
(or add GitDiffResponse to the existing import statement) so the
apiGitDiff(projectId: string) function can reference the type correctly; ensure
the import name matches the exported symbol from ./types.

@Rishabh-Bajpai

Copy link
Copy Markdown
Collaborator Author

PR Review: #7 - Enhance project features, UI improvements, and documentation updates

Branch: rishabh-developmentdevelopment
Commits: 27 | Files changed: 58 | +11,302 / -7,969
Date: May 20, 2026


Overview

This is a large feature PR that modularizes the backend route architecture, adds significant new features (git diff viewer, question workflow, notifications, session compaction, voice controls), and refactors the frontend with extracted utility modules and new components.


Architecture Changes

Backend Route Modularization

  • 2,486-line monolithic routes.py split into 10 focused modules:
    • files.py, messages.py, notifications.py, opencode.py, projects.py
    • runtime.py, scheduler_routes.py, sessions.py, stt.py, tasks.py
  • Uses dynamic module loading via importlib in routes.py
  • Each submodule uses a configure() pattern with module-level globals

Frontend Component Extraction

  • New utils: streamUtils.ts, messageUtils.ts, fileUtils.ts, formatting.ts, miscUtils.ts, taskUtils.ts, projectUtils.ts
  • New components: MessagePartCard, DiffPanel, QuestionCard, NotificationControls, RuntimeControls, ProjectFilesPanel, RalphLoopPanel, ScheduledTaskPanel
  • App.tsx reduced from ~3,300 lines to ~66 lines of imports (inline components removed)

Issues & Concerns

1. Backend route module pattern uses globals (Medium)

Each route module (notifications.py, sessions.py, etc.) stores app, settings, opencode_client, scheduler, voice_runtime as module-level globals via configure():

app = None
settings = None
opencode_client = None
scheduler = None
voice_runtime = None

def configure(app_instance, settings_instance, ...):
    global app, settings, opencode_client, ...
    app = app_instance
    ...

Why it matters: Fragile under concurrent requests, makes testing harder, and is not idiomatic Flask.

Recommendation: Use Flask's current_app and g patterns, or a class-based approach that injects dependencies.


2. Dynamic module loading in routes.py (Low-Medium)

_spec = spec_from_file_location(_PACKAGE_NAME, _PACKAGE_INIT, submodule_search_locations=[...])
_module = module_from_spec(_spec)
_spec.loader.exec_module(_module)

Why it matters: Unconventional for Flask. A simple from .routes import api_bp, register_api_routes would work since routes/__init__.py already exports these. Adds complexity without clear benefit.

Recommendation: Replace with direct imports.


3. Wildcard imports in route modules (Low)

Every route module does:

from .helpers import *  # noqa: F401,F403

Why it matters: Obscures dependencies, makes it hard to track what functions are available, and defeats static analysis tools.

Recommendation: Use explicit imports for the functions actually needed from helpers.py.


4. API signature change for sendMessage, abortSession, runCommand (Medium)

api.ts now accepts optional sessionId for these functions:

export async function sendMessage(projectId: string, text: string, sessionId?: string | null)
export async function abortSession(projectId: string, sessionId?: string | null)
export async function runCommand(projectId: string, command: string, argumentsList: string[], sessionId?: string | null)

Why it matters: All existing callers must pass the correct sessionId. If any call site is missed, the wrong session could be targeted.

Recommendation: Verify all call sites in App.tsx pass sessionId correctly. Add a lint rule or TypeScript check to ensure no call uses undefined unintentionally.


5. No tests (High)

58 files changed with 11,302+ lines added, zero tests.

Affected areas:

  • Stream parsing utilities (streamUtils.ts) — complex regex/JSON parsing
  • Message utilities (messageUtils.ts) — activity labeling, grouping logic
  • Backend routes — 10 new route modules with CRUD operations
  • Frontend components — MessagePartCard, DiffPanel, QuestionCard

Recommendation: At minimum, add unit tests for:

  • classifyStreamEvent() with various event types
  • extractMessagePartText() with malformed and valid payloads
  • buildGroupedTimelineEntries() with mixed message types
  • Backend route handlers (mocked OpenCode client)

6. Git diff endpoint generates patches in-memory (Low)

git_routes.py:git_diff reads untracked file contents and constructs patch lines manually:

content = Path(repo.working_dir, u).read_text(errors="replace")
lines = content.splitlines()
line_count = len(lines)
patch_lines = ["--- /dev/null", f"+++ b/{u}", f"@@ -0,0 +1,{line_count} @@"]

Why it matters: For large files (e.g., generated assets, lock files) this could consume significant memory.

Recommendation: Add file size limits or skip files above a threshold (e.g., 1MB).


7. QuestionCard uses index-based keys (Low)

key={`${question.id}:${questionIndex}`}

Why it matters: If questions are reordered or removed dynamically, React reconciliation could misbehave, causing stale state in option selections.

Recommendation: Use option labels or a combination of question ID + option label as keys where possible.


8. Commit history could be squashed (Low)

27 commits all from the same day with interleaved feat/refactor/fix/docs/style/chore commits.

Recommendation: Squash-merge or interactive rebase into logical groupings (e.g., one commit per feature area).


Positive Notes

  • Good separation of concerns with extracted utility modules
  • Stream event classification (classifyStreamEvent) is well-structured and handles multiple event format variations
  • TypeScript types are comprehensive for new features (QuestionRequest, GitDiffEntry, ToolPart, PatchPart, FilePart)
  • MessagePartCard handles multiple part types cleanly with dedicated renderers (ToolCallCard, PatchCard, FileCard, StepCard)
  • README and API documentation updates are thorough
  • .env.example updated with new NOTIFICATION_NTFY_TOPIC_URL
  • Incremental text delta updates via streamUtils.ts improve streaming UX
  • Session compaction/summarization endpoints properly delegate to OpenCode client

Summary

Category Count
High 1 (no tests)
Medium 2 (globals pattern, API signature change)
Low-Medium 1 (dynamic module loading)
Low 4 (wildcard imports, in-memory patches, React keys, commit history)

Recommendation: Address the globals pattern (#1) and add at least minimal tests (#5) before merging. The remaining items are improvements that can be done post-merge.

@Rishabh-Bajpai

Copy link
Copy Markdown
Collaborator Author

@copilot please review this PR, find issues and then address them and all comments to this PR (if required)

Copilot AI and others added 3 commits May 20, 2026 23:46
Agent-Logs-Url: https://github.com/Rishabh-Bajpai/mobile-opencode-control/sessions/21222144-c657-46cf-957d-18faf75e6449

Co-authored-by: Rishabh-Bajpai <28703138+Rishabh-Bajpai@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Rishabh-Bajpai/mobile-opencode-control/sessions/21222144-c657-46cf-957d-18faf75e6449

Co-authored-by: Rishabh-Bajpai <28703138+Rishabh-Bajpai@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Rishabh-Bajpai/mobile-opencode-control/sessions/21222144-c657-46cf-957d-18faf75e6449

Co-authored-by: Rishabh-Bajpai <28703138+Rishabh-Bajpai@users.noreply.github.com>

Copilot AI commented May 20, 2026

Copy link
Copy Markdown
Contributor

PR Review: #7 - Enhance project features, UI improvements, and documentation updates

Branch: rishabh-developmentdevelopment
...

Addressed the actionable items in 1614650: added regression coverage for the git diff endpoint, capped large untracked diff previews, restored _utc_now compatibility so the modularized task routes pass the existing scheduler tests again, replaced the QuestionCard index key with a stable content-based key, and fixed the frontend build regressions in App.tsx, api.ts, RichMessageText, and the diff badge styling. I kept the larger globals/dynamic-loading refactors out of this PR to keep the changes surgical. Screenshot: /tmp/pr-fix-screenshot.png (local fixture-mode capture used for visual verification).

@copilot please review this PR, find issues and then address them and all comments to this PR (if required)

Reviewed the PR and addressed the concrete issues in 1614650: fixed the branch-local frontend build regressions, added backend regression tests for git diff handling, added a size guard for large untracked diff previews, tightened the QuestionCard keying, and restored compatibility for the existing scheduler test after the route split. Validation passed with python -m pytest backend/tests -q and npm --prefix frontend run build. Screenshot: /tmp/pr-fix-screenshot.png (local fixture-mode capture used for visual verification).

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • accounts.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • android.clients.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • clients2.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • clientservices.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • content-autofill.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7621 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.PKqtku --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,2913196492794835983,14005965014070131799,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16468100570178202403,15202697116569250205,4 --trace-process-track-uuid=3190708989122997041 -opencode-control/frontend/node_f00171c66136110c56c0976d3509343907223d7f:backend/app/routes/tasks.py uname (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --no-sandbox --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png REDACTED /bin/java 1 1 (dns block)
  • optimizationguide-pa.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • redirector.gvt1.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • safebrowsingohttpgateway.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • update.googleapis.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --no-sandbox --use-angle=swiftshader-webgl --crashpad-handler-pid=7276 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.vJ41PJ --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,17141800864827408245,3015326661049787276,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,16216157283293138560,1300998522976237971,4 --trace-process-track-uuid=3190708989122997041 --host /opt/google/chrome/chrome_crashp--cached (dns block)
  • www.google.com
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190708989122997041 (dns block)
    • Triggering command: /opt/google/chrome/chrome /usr/bin/google-chrome --headless=new --disable-gpu --window-size=1440,1200 --screenshot=/tmp/pr-fix-screenshot.png --virtual-time-budget=8000 REDACTED cal/bin/git (dns block)
    • Triggering command: /proc/self/exe /proc/self/exe --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --use-angle=swiftshader-webgl --crashpad-handler-pid=7036 --enable-crash-reporter=, --noerrdialogs --user-data-dir=/tmp/com.google.Chrome.scoped_dir.hcs7pM --change-stack-guard-on-fork=enable --shared-files=v8_context_snapshot_data:100 --field-trial-handle=3,i,5534394237042052536,7373889823583289584,262144 --disable-features=PaintHolding --variations-seed-version --pseudonymization-salt-handle=7,i,14699863198636340381,15719531884773106172,4 --trace-process-track-uuid=3190709000367499229 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@Rishabh-Bajpai
Rishabh-Bajpai merged commit 2f019cb into development May 20, 2026
1 check passed
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.

2 participants