Files

11 KiB

APP-3773: Image attachments in the feedback skill — technical plan

Linear: https://linear.app/warpdotdev/issue/APP-3773/add-support-for-image-uploads-in-the-feedback-skill See PRODUCT.md for user-facing behavior.

Context

This feature touches three surfaces: the Warp client's slash-command → skill invocation pipeline, the bundled feedback skill's instructions, and the feedback skill's filing helper script. The skill-invocation entry point is in the slash-command controller. app/src/ai/blocklist/controller/slash_command.rs:74 builds the context that accompanies every slash-command request:

let context = input_context_for_request(
    /* is_user_query = */ false,
    controller.context_model.as_ref(ctx),
    ...
);

The is_user_query: false argument is the root cause of the "attachments don't reach the skill" bug. input_context_for_request in the same module delegates to BlocklistAIContextModel::pending_context in app/src/ai/blocklist/context_model.rs, which gates user-attached items — including AIAgentContext::Image entries built from pending_attachments — behind that flag. Non-slash-command user queries pass true here; all slash commands, including InvokeSkill, pass false. As a result, pending images, pending selected text, pending context blocks, and auto-attached agent-view blocks are stripped from the skill's context before it reaches the agent. The relevant image carrier is ImageContext in app/src/ai/agent/mod.rs:1893:

pub struct ImageContext {
    pub data: String,        // base64-encoded image data
    pub mime_type: String,
    pub file_name: String,
    pub is_figma: bool,
}

It holds an in-memory base64 blob and a filename string, but no on-disk path. This is why the filing script cannot receive a path to the attached image — there isn't one. The scoped design in PRODUCT.md avoids this by never passing image bytes or paths to the script at all. The feedback skill lives at resources/channel-gated-skills/dogfood/feedback/ and ships two artifacts that matter here: SKILL.md (agent instructions) and scripts/file_feedback_issue.py (the filing helper). The script's top-level control flow is in main():

  • If gh_path_if_authenticated() returns a usable path, call create_issue_with_gh and print a created (or failed) result.
  • Otherwise, print an unavailable result and exit. The filing script currently has no concept of "attachments are present," no browser path at all, and no way for the agent to pick a filing method explicitly. It also exposes a --dry-run flag that no caller actually uses.

Proposed changes

Three landable changes, ordered by how independently each can ship.

1. Platform: let skill invocations see user-attached images

In app/src/ai/blocklist/controller/slash_command.rs, change the is_user_query argument passed to input_context_for_request for SlashCommandRequest::InvokeSkill specifically. Other slash-command variants continue to pass false. There are two reasonable shapes; pick the narrower one:

  • Preferred: Branch on SlashCommandRequest::InvokeSkill when building context and pass true only for that variant. Smallest behavior change; no new parameters on input_context_for_request.
  • Alternative: Add an include_user_attachments: bool parameter to input_context_for_request (or to pending_context) that is orthogonal to is_user_query, and pass true for InvokeSkill. Use this only if we discover we want images but not blocks or selected text on skill invocations. The product spec doesn't currently require that separation, so the preferred path is simpler. Reads / writes on pending_attachments in BlocklistAIContextModel do not need to change. pending_context already emits AIAgentContext::Image(image.clone()) for each PendingAttachment::Image entry; the fix just stops hiding that output behind the slash-command gate. No server-side change is required. AIAgentContext::Image is already serialized over the existing multi-agent API and rendered as multimodal input to the model.

2. Script: required --use {gh|browser} flag in file_feedback_issue.py

Replace the current implicit fallback logic in main() with an explicit, caller-selected method:

  • New CLI argument: --use (required, choices=["gh", "browser"], dest="use_method"). The caller (the skill) must pass one of the two values; argparse enforces the constraint and rejects anything else.
  • Remove the previously-existing --dry-run flag. The skill does not use it, and leaving it in creates an unused branch the caller has to reason about.
  • Split main() into two helper functions: file_with_gh(title, body) for the --use gh path and fallback_to_browser(title, body) for the --use browser path. main() becomes a thin dispatcher: if args.use_method == "browser": fallback_to_browser(...) else file_with_gh(...).
  • file_with_gh preserves today's behavior: returns status: "created" on success, status: "unavailable" when gh is missing or unauthenticated, and status: "failed" with a gh_error on error. It does not automatically fall back to the browser; if the caller wants browser, it must pass --use browser.
  • fallback_to_browser is simplified. It no longer takes a has_attachments parameter: the browser path is only used by the skill when image attachments are present, so its user-facing message text and failure errors always reference pasting/dropping screenshots. The URL-prefill vs. body-in-payload branching is preserved: when the full URL would exceed MAX_PREFILL_URL_LENGTH, the body is returned under a body field in the JSON result and only the title is prefilled in the URL.
  • Do not add a forced_browser (or equivalent) field to the JSON result payload. Per PRODUCT.md invariant 16, no telemetry is captured from this feature; keeping the payload shape unchanged avoids creating a latent telemetry hook that would need to be wired up or removed later. No changes are needed in the script's gh, browser, or URL helpers themselves. The existing browser_is_available() gate continues to cover headless sessions and will produce a failure payload the agent can surface verbatim; the only adjustment is that the failure message always mentions image attachments, since --use browser implies they are present.

3. Skill: instruct the agent on the image-attached branch

Edit SKILL.md in the feedback skill to add a short, explicit conditional that runs after duplicate detection and before filing, and update the Output section to describe the required --use flag:

  • If the user's query includes one or more image attachments (visible in multimodal context), the agent must:
    • Draft the issue body as normal, including a short, plain-language description of each attached image's content in the relevant Behavior/Problem/Actual-behavior/Artifacts section.
    • In the Artifacts section, emit one placeholder per attached image, one per line, in the order the images were attached. The placeholder text should be recognizable to a human reader as a placeholder (e.g. _Paste screenshot here_). The skill does not need to use any particular sentinel; the goal is that the user can see where to drop.
    • Invoke file_feedback_issue.py with --use browser (instead of --use gh) alongside the existing --title and --body-file arguments.
    • In the final user-visible response, combine the standard browser-opened (or body-in-payload) messaging with an explicit instruction to paste or drag each attached image into the placeholder line(s) before submitting the issue. Reference the count of attached images so the user knows how many to paste.
  • If the user's query has no image attachments, pass --use gh and file via the gh CLI path as today. No placeholders, no drag-and-drop instructions. The rest of the skill's workflow (classification, clarifying questions, grounded references, duplicate detection, issue structure, output handling for created / browser_opened / unavailable / failed) is unchanged. The image-attached branch is additive.

Risks and mitigations

  • Broader effect of the platform flag change. Passing is_user_query: true for InvokeSkill also exposes pending blocks and selected text to skills, not just images. This is almost certainly desirable (skills today silently lose that context too), but it is a behavior change for every existing skill invocation. Mitigation: land the platform fix behind a feature flag if the blast radius is a concern, or gate the change to image attachments specifically via the alternative include_user_attachments parameter described in Proposed changes #1.
  • User submits with an empty placeholder. If the user forgets to drop the image, the issue will contain a literal _Paste screenshot here_ line. Mitigation: keep the placeholder text short and obviously a placeholder; rely on the agent's in-body prose description as the authoritative content. Acceptable failure mode per PRODUCT.md #12.
  • GitHub web UI changes its drag-drop behavior. The user-attachments upload flow is internal to GitHub and not a public API. A change to that surface could break the "drop into the body" step. Mitigation: none required at this layer — if GitHub's web UI stops accepting drops, the whole web-UI workflow breaks for everyone and is not specific to this feature.
  • Forced-browser path is slower than gh issue create. Users with gh authenticated lose the one-shot filing speed when they attach images. Mitigation: document in the skill's response so the user understands why the browser opened. No telemetry is captured for this feature (PRODUCT.md invariant 16), so usage-based revisiting relies on qualitative signals (user reports, direct feedback on the feedback skill itself) rather than measurement. If that turns out to be insufficient, adding measurement is a separate, explicitly-scoped change.

Follow-ups

  • If the forced-browser path becomes the dominant filing path due to screenshots being common in feedback, reassess the decision to keep gh issue create at all. Unifying on a single path (browser or CLI) reduces user-facing branching in the skill's final response and collapses the test matrix.
  • If another skill later wants the same "caller picks filing method" signal (for example, a future /bug or /support skill), consider lifting --use {gh|browser} into a shared filing helper rather than duplicating the flag semantics per skill.
  • Consider exposing the image-attached placeholder convention as a named sentinel (for example, <!-- warp-feedback:image-N -->) if we ever want to post-process the submitted issue to validate that users replaced the placeholders before submission.