Skip to content

feat(session): claude attachment --add-dir + settings-derived spawn mode (ELECTRON-3R4) - #689

Open
BlackKeyZ wants to merge 2 commits into
mainfrom
feat/claude-adddir-mode-seed
Open

feat(session): claude attachment --add-dir + settings-derived spawn mode (ELECTRON-3R4)#689
BlackKeyZ wants to merge 2 commits into
mainfrom
feat/claude-adddir-mode-seed

Conversation

@BlackKeyZ

Copy link
Copy Markdown
Collaborator

Problem

Sentry ELECTRON-3R4 (user feedback, 2.1.41): a pasted image displays in the UI but the agent never recognizes it, while absolute-path images work.

Root cause: pasted/uploaded attachments are staged in $TMPDIR/aionui/<conv>/ (home-page uploads in .../general) — outside the workspace cwd — and delivered to the model as a path reference. Under the fail-closed --permission-mode default, the model's out-of-cwd Read of that path raises a permission prompt; denied/unanswered, the attachment is simply "unreadable". Live-probed end to end (claude 2.1.220): the identical frame succeeds with --add-dir and dead-ends without it.

A second, related regression: the direct-CLI path hard-coded --permission-mode default whenever no mode was persisted, silently overriding the user's machine-level claude settings permissions.defaultMode — the legacy ACP adapter resolved that setting per session.

Fix

F1 — grant exactly the two upload staging dirs at spawn

  • New aionui_common::paths::uploads_dir(Option<&str>): single source of the upload layout; aionui-file rewired onto it (byte-identical layout).
  • The claude open path appends --add-dir <tmp>/aionui/<conv> + --add-dir <tmp>/aionui/general (mkdir-before-spawn, WARN-only on failure). Fresh, Resume and the wake-recipe respawn all inherit the grant (shared init args). Everything else stays fail-closed.

F2 — spawn mode honors claude settings permissions.defaultMode

  • New aionui_session::claude_settings resolver — precedence + alias table verbatim from the official adapter 0.33.1 (managed > local project > project > user; fallback "default").
  • build_session_instance applies it as the LAST precedence step (persisted switch > create-time seed > settings), still passing an explicit flag.
  • No bespoke persist/UI writes needed: open seeds caps.current_mode from config.mode (picker shows the value from the first runtime/ensure), and claude's init echo persists via the existing sniff_modeConfigChangedsave_runtime_state chain.
  • Drift tripwire: WARN when the first observed permissionMode differs from the spawn-requested flag value.
  • Clean-env probe (2.1.220): omitting the flag makes the CLI resolve its own settings hierarchy — NOT a hard bypassPermissions default; the stale comment claiming otherwise is rewritten with corrected evidence.

With no settings present, emitted spawn args are byte-identical to before except the two --add-dir pairs (pinned-args golden test re-pinned).

Tests

  • claude_settings: 7 unit tests (precedence matrix, alias normalization, bad JSON fall-through, on-disk layering, spawn-whitelist invariant).
  • open_session_grants_add_dir_for_upload_dirs / ..._resume_grants_same_add_dirs: both spawn branches emit the two pairs, dirs created up-front.
  • claude_mode_fallback_applies_only_when_unset: fallback fires only for claude with no resolved mode; resolver provably not invoked otherwise.
  • open_seeds_current_mode_from_config_before_any_wire_frame: picker truth at open, before any wire frame.
  • sniff_mode_warns_when_first_observed_mode_differs_from_requested: exactly one drift WARN on first-observation mismatch, none on match, behavior unchanged.
  • Full pre-push gate green: 7492 tests passed, clippy -D warnings, fmt.

blackkey added 2 commits July 27, 2026 15:14
…ode (ELECTRON-3R4)

Pasted/uploaded attachments land in $TMPDIR/aionui/<conv>/ (home-page
uploads in .../general) — outside the workspace cwd, so under the
fail-closed `--permission-mode default` the model's Read of an
attachment dead-ends on an out-of-cwd permission prompt: the pasted
image displays in the UI but is never recognized by the agent.

F1 — grant exactly the two upload staging dirs at spawn:
- aionui-common: new `paths::uploads_dir(Option<&str>)`, the single
  source of the upload layout; aionui-file rewired onto it
  (byte-identical layout).
- The claude open path appends `--add-dir <tmp>/aionui/<conv>` +
  `--add-dir <tmp>/aionui/general` (mkdir-before-spawn, WARN-only on
  failure). Fresh, Resume and the wake-recipe respawn all inherit the
  grant (shared init args).

F2 — spawn mode honors claude settings `permissions.defaultMode`:
- The direct-CLI path hard-coded `--permission-mode default` whenever
  no mode was persisted, silently overriding the user's machine-level
  settings — a regression against the legacy ACP adapter, which
  resolved that setting per session.
- New `aionui_session::claude_settings` resolver: precedence + alias
  table verbatim from the official adapter 0.33.1 (managed > local
  project > project > user; fallback "default").
- `build_session_instance` applies it as the LAST precedence step
  (snapshot > config > settings), still passing an explicit flag.
  No bespoke persist/UI writes: open seeds caps.current_mode from
  config.mode, and claude's init echo persists via the existing
  sniff_mode → ConfigChanged → save_runtime_state chain.
- Drift tripwire: WARN when the first observed permissionMode differs
  from the spawn-requested flag value.
- Clean-env probe (2.1.220): omitting the flag makes the CLI resolve
  its own settings hierarchy — NOT a hard bypassPermissions default;
  the stale comment claiming otherwise is rewritten with the corrected
  evidence.
…RON-3R4)

Productionizes the fix-validation probe as an #[ignore] live test: the real
ClaudeConnection::open_session + dispatch(Send) path, blank config (fail-closed
--permission-mode default), an image staged in the out-of-cwd upload dir, the
exact production ResourceLink lowering. Asserts the turn completes with ZERO
permission prompts and the model answers from the image content — the frame
that dead-ended on a can_use_tool(Read) ask before the --add-dir grant.

Verified against claude 2.1.220: passes in ~37s.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant