From 6fcc2b74c880becb05a738f4eed35e5e054fd2d0 Mon Sep 17 00:00:00 2001 From: BeLazy167 Date: Tue, 25 Aug 2026 14:39:46 -0500 Subject: [PATCH 1/2] Offer every model at install time, and stop the Keychain prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two install-time reports from the same session: - The model chooser listed only the four Claude models. It reads /v1/models — the Anthropic surface, which by design serves only Claude — while the GPT models live on the OpenAI-compat surface. It now reads /models, the same endpoint models.tsv already uses, so all 12 appear and the default can be a GPT model. - macOS Keychain prompted "password data for new item:" mid-install and blocked. The write used bare `-w`, which relies on security reading the password from stdin — undocumented, and it falls back to a /dev/tty prompt when it does not. Not reproducible here (verified both plain and `cat script | bash`), so it is environment-specific, most likely a locked keychain. `security -i` takes the command on stdin, cannot fall back to a prompt, and keeps the key out of the process argument list. The failure message now names the locked-keychain case. Verified: install exits 0 and the chooser offers all 12 models. --- install.sh | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/install.sh b/install.sh index b9635b2..17b2f2c 100755 --- a/install.sh +++ b/install.sh @@ -170,7 +170,13 @@ adopt_legacy_profile() { # adopt_legacy_profile if [ -n "$token" ]; then if [ "${OS:-linux}" = darwin ]; then account="${USER:-$(id -un)}"; secret_ref='qbraid-code:default' - printf '%s\n%s\n' "$token" "$token" | security add-generic-password -U -a "$account" -s "$secret_ref" -w >/dev/null 2>&1 \ + # `security -i` takes the whole command on stdin. Bare `-w` relies on + # security reading the password from stdin, which is undocumented and + # falls back to a /dev/tty prompt when it does not — under `curl | bash` + # that surfaces as "password data for new item:" and blocks the install. + # -i also keeps the secret out of the process argument list. + printf 'add-generic-password -U -a %s -s %s -w %s\n' "$account" "$secret_ref" "$token" \ + | security -i >/dev/null 2>&1 \ || { rm -rf "$stage"; die "could not migrate the default key into macOS Keychain."; } printf 'QBRAID_CODE_SECRET_BACKEND=keychain\nQBRAID_CODE_SECRET_REF=%s\n' "$secret_ref" >> "$stage/env" elif command -v secret-tool >/dev/null 2>&1 && printf '%s' "$token" | secret-tool store --label='qbraid-code default profile' service qbraid-code ref qbraid-code:default >/dev/null 2>&1; then @@ -755,8 +761,11 @@ store_profile_secret() { SECRET_REF="qbraid-code:$PROFILE:$GENERATION" if [ "$OS" = darwin ]; then command -v security >/dev/null 2>&1 || die "macOS Keychain is unavailable." - printf '%s\n%s\n' "$API_KEY" "$API_KEY" | security add-generic-password -U -a "${USER:-$(id -un)}" -s "$SECRET_REF" -w >/dev/null 2>&1 \ - || die "could not store the profile key in macOS Keychain." + # See the migration path above: `security -i` cannot fall back to a tty + # prompt, and keeps the key out of argv. + printf 'add-generic-password -U -a %s -s %s -w %s\n' "${USER:-$(id -un)}" "$SECRET_REF" "$API_KEY" \ + | security -i >/dev/null 2>&1 \ + || die "could not store the profile key in macOS Keychain. If your login keychain is locked, unlock it in Keychain Access and re-run." SECRET_BACKEND="keychain" elif command -v secret-tool >/dev/null 2>&1 && printf '%s' "$API_KEY" | secret-tool store --label="qbraid-code $PROFILE profile" service qbraid-code ref "qbraid-code:$PROFILE:$GENERATION" >/dev/null 2>&1; then SECRET_REF="qbraid-code:$PROFILE:$GENERATION" @@ -1024,7 +1033,10 @@ MODEL="${QBRAID_CODE_MODEL:-}" if [ "$UPDATE_KEY" -eq 1 ] && [ -z "$MODEL" ]; then MODEL="$OLD_MODEL"; fi if [ -z "$MODEL" ]; then # The list is fetched live so new gateway models appear without a release here. - api_get "$GATEWAY_URL/v1/models" "$API_KEY" + # /models (OpenAI-compat surface), NOT /v1/models (Anthropic surface): the + # Anthropic one lists only the Claude models, so the chooser silently hid + # every GPT model the launcher can actually run. + api_get "$GATEWAY_URL/models" "$API_KEY" MODEL_IDS=$(set +o pipefail; printf '%s' "$API_BODY" | grep -o '"id":"[^"]*"' | sed 's/"id":"//; s/"$//') if [ -z "$MODEL_IDS" ]; then warn "could not list models — defaulting to claude-sonnet-4-6" From fb7dbf3a6e69da4132673529ee6a0ad79c2e7193 Mon Sep 17 00:00:00 2001 From: BeLazy167 Date: Tue, 25 Aug 2026 14:43:52 -0500 Subject: [PATCH 2/2] Teach the security test double the -i call shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stub read the action from $1, so `security -i` (command on stdin) fell through and stored nothing — failing the generation-rotation assertions. It now models both shapes the installer uses. --- tests/install-profiles.sh | 43 +++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/tests/install-profiles.sh b/tests/install-profiles.sh index ac0e1c9..a85bd6a 100755 --- a/tests/install-profiles.sh +++ b/tests/install-profiles.sh @@ -51,14 +51,41 @@ EOF chmod +x "$FAKE_BIN/secret-tool" cat > "$FAKE_BIN/security" <<'EOF' #!/usr/bin/env bash -action="$1"; shift; service="" -while [ "$#" -gt 0 ]; do case "$1" in -s) service="$2"; shift 2;; -w) want_password=1; shift;; *) shift;; esac; done -file="$HOME/.fake-key.$(printf '%s' "$service" | tr ':/' '__')" -case "$action" in - add-generic-password) IFS= read -r value; printf '%s\n' "$value" > "$file" ;; - find-generic-password) cat "$file" ;; - delete-generic-password) rm -f "$file" ;; -esac +# Models both call shapes the installer uses: +# security -s -w (password on stdin) +# security -i (whole command on stdin, with +# the password as the -w value) +# The second is what the real tool needs so it can never fall back to a tty +# prompt; a stub that only knew the first silently stored nothing. +run_command() { # run_command + local action="$1"; shift + local service="" password="" have_password=0 + while [ "$#" -gt 0 ]; do + case "$1" in + -s) service="$2"; shift 2 ;; + -w) if [ "$#" -ge 2 ] && [ "${2#-}" = "$2" ]; then password="$2"; have_password=1; shift 2; else shift; fi ;; + *) shift ;; + esac + done + local file="$HOME/.fake-key.$(printf '%s' "$service" | tr ':/' '__')" + case "$action" in + add-generic-password) + [ "$have_password" -eq 1 ] || IFS= read -r password + printf '%s\n' "$password" > "$file" ;; + find-generic-password) cat "$file" ;; + delete-generic-password) rm -f "$file" ;; + esac +} + +if [ "$1" = "-i" ]; then + while IFS= read -r line; do + [ -n "$line" ] || continue + # shellcheck disable=SC2086 + run_command $line + done +else + run_command "$@" +fi EOF chmod +x "$FAKE_BIN/claude" "$FAKE_BIN/curl" "$FAKE_BIN/security" "$FAKE_BIN/cliproxyapi" export HOME="$HOME_ROOT" QBRAID_CODE_HOME="$QC_HOME" QBRAID_CODE_BIN_DIR="$BIN_DIR"