Skip to content

fix: v0.38.0 — concurrency, API robustness, audit coverage, teaching drift, frontend UX#124

Merged
hxddh merged 1 commit into
mainfrom
claude/architecture-code-docs-review-k41dn6
Jul 17, 2026
Merged

fix: v0.38.0 — concurrency, API robustness, audit coverage, teaching drift, frontend UX#124
hxddh merged 1 commit into
mainfrom
claude/architecture-code-docs-review-k41dn6

Conversation

@hxddh

@hxddh hxddh commented Jul 17, 2026

Copy link
Copy Markdown
Owner

What changed

A four-angle mining round (frontend, router/API + SSE, agent-teaching drift, audit coverage + concurrency) turned up ~28 real items. Every security-floor bound is untouched.

Concurrency

  • discard() no longer hangs the session's next turn 120 s. A clean failure (fresh-install "no model key") left the session-active pointer on a handle whose done_event was never set; discard() now resolves the handle + clears the pointer. (Independently found by two mining agents; reproduced.)
  • Retried run gets a fresh SSE streambus.create() resets a previously-done entry instead of replaying the old failure + instant close.
  • set_result/fail are session-bound — a turn_id collision can't deliver session A's result to session B.
  • A missing client turn_id is synthesized server-side so per-session serialization always applies.

API layer

  • Upload named ./.. no longer 500s (sanitizer maps to a safe default).
  • GET /runs/{id}/events 404s an unknown run (was 200 + instant "done").
  • Run that fails to launch after the atomic claim reverts to pending (was wedged running).
  • Upload racing a session-delete cleans up + returns 409 (was orphaned tree + 500).
  • config.scrub_paths collapses the data/home dirs out of surfaced error text (redact_text scrubbed secrets, not paths).

Audit (rule 17)

  • Dataset import audited at the import (was only on a successful analyze); diagnostic report gen routed through the audited tool path; session report / run create+start / compare_to_last_survey / list_uploaded_files now audited.

Agent-teaching drift (from the v0.37 tool changes)

  • test_conditional_get docstring + data-consistency skill + S3 docstring + trace label all still taught "HTTP 200 → object changed" — but v0.37 made 200-with-same-ETag mean "provider ignored If-None-Match" (unchanged + provider_unsupported). Corrected to key off etag_matches.
  • list_objects drops the stale 500-cap teaching; lifecycle-cost skill documents the new required prefix; query_account_profile public_buckets help fixed; skills_used cap tracks _MAX_SKILL_LOADS (10 → 20).

Data race

  • mark_imported guarded by expected stored_path — a re-upload mid-analysis can't stamp a stale table as imported.

Frontend

  • Stop during a slow tool call waits for the persisted partial (no vanished turn); a failure while viewing another session keeps the message; steer preserves the composer draft; session switch no longer flashes prior content; rename refreshes the header; blocking fallback keeps the stopped marker; reload race no longer mislabels a healthy session stalled; KiB/MiB labels; model-404 hint only for provider-shaped errors; evidence dialog Escape (idle) + busy-guarded ✕; blank model-provider name validation.

Checks run

  • cd sidecar && pytest -q642 passed (new test_v0380_fixes.py; diagnostic/SSE/cap tests updated where behavior intentionally changed).
  • ruff check app — clean.
  • cd frontend && npx tsc --noEmit and npm run build — clean.

Known gaps / deferred

  • Two Tier-3 items were intentionally deferred (not in this PR): the event-bus never-done growth backstop and making the blocking handler async to avoid threadpool occupancy — the latter is a larger structural change.
  • parallel_tool_calls=False stays global (real DeepSeek workaround).

🤖 Generated with Claude Code

https://claude.ai/code/session_01M3YojFPzjkfMj6YYSwvzdx


Generated by Claude Code

…drift, frontend UX

Concurrency: turn_guard.discard() now resolves the handle + clears the
session-active pointer (a failed turn no longer hangs the session's next turn
120s); bus.create() resets a re-created done run so a retry gets a fresh SSE
stream; set_result/fail are session-bound (no cross-session result on a turn_id
collision); a missing client turn_id is synthesized server-side so serialization
always applies.

API layer: _safe_filename maps "."/".."/"" to a safe default (no os.replace onto
a dir → 500); GET /runs/{id}/events 404s an unknown run; a run that fails to
launch after the atomic claim reverts to pending; upload racing a session-delete
cleans up + 409s instead of orphaning a tree; config.scrub_paths collapses the
data/home dirs out of surfaced error text.

Audit (rule 17): dataset import audited at the import; diagnostic report gen
routed through the audited tool path; session report / run create+start /
compare_to_last_survey / list_uploaded_files audited.

Teaching drift (from v0.37 tool changes): test_conditional_get docstring + skill
+ S3 docstring + trace label corrected for the 200-same-ETag = provider-ignored
semantics; list_objects drops the stale 500-cap teaching; lifecycle-cost skill
documents the new list_multipart_uploads prefix; query_account_profile
public_buckets help fixed; skills_used cap tracks _MAX_SKILL_LOADS (20).

Data race: mark_imported guarded by expected stored_path so a re-upload
mid-analysis can't stamp a stale table imported.

Frontend: Stop during a slow tool call waits for the persisted partial (no
vanished turn); a failure while viewing another session keeps the message; steer
preserves the composer draft; session switch no longer flashes prior content;
rename refreshes the header; blocking fallback keeps the stopped marker; reload
race no longer mislabels a healthy session stalled; KiB/MiB labels; model-404
hint only for provider-shaped errors; evidence dialog Escape + busy-guarded ✕;
model-provider blank-name validation.

642 sidecar tests pass (new test_v0380_fixes.py); ruff clean; frontend tsc +
build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3YojFPzjkfMj6YYSwvzdx

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b39f079e3c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +127 to +130
run_tool_with_events(
conn, run_id, "generate_markdown_report", {"run_id": run_id},
lambda: {"report_path": config.rel_path(
write_report(run, evidence, findings, summary)[0]), "format": "markdown"},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fail the run when diagnostic report generation fails

When write_report raises (for example, because the disk is full or the report directory is unwritable), run_tool_with_events catches the exception and returns a success: false result rather than re-raising it. Because this result is ignored, the body returns its summary and run_executor marks the diagnostic completed and records a report path even though no report exists. Require success here, as the analysis executors do, so report-generation failures make the run fail instead of exposing a broken completed report.

Useful? React with 👍 / 👎.

Comment on lines +112 to +115
"UPDATE session_datasets SET duckdb_path=?, table_name=?, row_count=?, "
"detected_format=?, status='imported' WHERE id=? AND stored_path=?",
(duckdb_path_rel, table_name, row_count, detected_format,
dataset_id, expected_stored_path),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Version uploads instead of comparing the stable path

This predicate does not detect the concurrent re-upload it is intended to guard: uploading the same filename in the same session always writes to the same sessions/{session_id}/raw/{filename} path, and upsert reuses that exact stored_path. If a re-upload resets the row after the old file has been imported but before this update, the path still matches, so the stale DuckDB table is marked imported and reused for the new upload. Compare a changing generation/content identifier or give each upload a unique stored path.

Useful? React with 👍 / 👎.

Comment on lines +104 to +106
audit.record(conn, "run.create",
{"run_id": run_id, "run_type": body.run_type,
"provider_id": body.provider_id, "bucket": body.bucket}, run_id=run_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Commit the run creation audit record

audit.record explicitly does not commit, and this handler performs only a read before returning after inserting this new record. The request dependency then closes the connection, rolling the audit insert back, so every successful POST /runs still lacks the promised run.create audit event. Commit after recording it.

Useful? React with 👍 / 👎.

@hxddh
hxddh merged commit f8457a6 into main Jul 17, 2026
6 checks 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