Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,24 @@ jobs:
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
security list-keychains -d user -s build.keychain

- name: Import provisioning profile
if: steps.guard_release_assets.outputs.skip_all != 'true'
env:
APPLE_PROVISION_PROFILE_BASE64: ${{ secrets.APPLE_PROVISION_PROFILE_BASE64 }}
run: |
# Optional secret: only set once a Developer ID provisioning profile exists for
# a restricted entitlement (e.g. CloudKit). See plans/golden-tumbling-gray.md M3
# for the one-time Developer portal setup this unblocks. Absent today by design —
# when unset, this step is a no-op and the release pipeline (and the signing
# script it feeds) behaves exactly as it does today: no profile, no iCloud.
if [ -z "$APPLE_PROVISION_PROFILE_BASE64" ]; then
echo "No APPLE_PROVISION_PROFILE_BASE64 secret set; skipping profile embedding."
echo "PROGRAMA_PROVISION_PROFILE=" >> "$GITHUB_ENV"
exit 0
fi
echo "$APPLE_PROVISION_PROFILE_BASE64" | base64 --decode > /tmp/programa.provisionprofile
echo "PROGRAMA_PROVISION_PROFILE=/tmp/programa.provisionprofile" >> "$GITHUB_ENV"

- name: Codesign app
if: steps.guard_release_assets.outputs.skip_all != 'true'
env:
Expand All @@ -407,6 +425,7 @@ jobs:
HELPER_PATH="$APP_PATH/Contents/Resources/bin/ghostty"
./scripts/sign-release-app.sh "$APP_PATH" "$APPLE_SIGNING_IDENTITY" programa.entitlements
./scripts/verify-release-entitlements.sh "$APP_PATH" "$CLI_PATH" "$HELPER_PATH"
./scripts/verify-provision-profile.sh "$APP_PATH"

- name: Verify embedded Sparkle artifact
if: steps.guard_release_assets.outputs.skip_all != 'true'
Expand Down Expand Up @@ -610,3 +629,4 @@ jobs:
run: |
security delete-keychain build.keychain >/dev/null 2>&1 || true
rm -f /tmp/cert.p12
rm -f /tmp/programa.provisionprofile
29 changes: 27 additions & 2 deletions CLI/CLI+Hooks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -465,11 +465,36 @@ extension ProgramaCLI {
// case that leaves them stuck on.
do {
let mappedSession = parsedInput.sessionId.flatMap { try? sessionStore.lookup(sessionId: $0) }
let workspaceId = try resolvePreferredWorkspaceIdForClaudeHook(

// Deliberately not `try`, for the same reason as the surfaceId resolution
// further down -- and this was the remaining half of that bug. A throw here
// aborted the hook before any of the three clears below, and the catch at the
// bottom treats teardown-shaped errors as benign: it prints "OK", so Claude
// Code sees a perfectly healthy hook while the red "blocked" badge stays lit
// with nothing left to turn it off.
//
// Nothing recovers from that. There is no TTL or watchdog on agent state, and
// AgentScreenDetectionEngine deliberately refuses to touch a surface a hook has
// claimed (its `hooksOwned` guard), so the terminal can be visibly running a
// command while the sidebar still reads "Claude needs your permission" until the
// session ends.
//
// Falling back to the identifiers the Notification hook recorded is the whole
// point: those are the exact workspace and surface it marked blocked, so they
// are the right things to clear even when a live re-resolution is unavailable.
let resolvedWorkspaceId = (try? resolvePreferredWorkspaceIdForClaudeHook(
preferred: mappedSession?.workspaceId,
fallback: workspaceArg,
client: client
)
))
?? nonEmptyClaudeHookIdentifier(mappedSession?.workspaceId).flatMap { isUUID($0) ? $0 : nil }
?? nonEmptyClaudeHookIdentifier(workspaceArg).flatMap { isUUID($0) ? $0 : nil }

// Only when there is genuinely no workspace to name is there nothing to clear.
guard let workspaceId = resolvedWorkspaceId else {
print("OK")
return
}
let claudePid = mappedSession?.pid

// AskUserQuestion means Claude is about to ask the user something.
Expand Down
4 changes: 4 additions & 0 deletions GhosttyTabs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
MOBB0003 /* MobileBridgeStreamSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = MOBB0004 /* MobileBridgeStreamSupport.swift */; };
MOBB0005 /* MobileBridgeListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = MOBB0006 /* MobileBridgeListener.swift */; };
MOBB0007 /* MobileBridgeSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = MOBB0008 /* MobileBridgeSession.swift */; };
MOBB0009 /* MobileBridgePush.swift in Sources */ = {isa = PBXBuildFile; fileRef = MOBB0010 /* MobileBridgePush.swift */; };
A5001100 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5001101 /* Assets.xcassets */; };
A5001230 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A5001231 /* Sparkle */; };
MOBB1003 /* IrohLib in Frameworks */ = {isa = PBXBuildFile; productRef = MOBB1002 /* IrohLib */; };
Expand Down Expand Up @@ -604,6 +605,7 @@
MOBB0004 /* MobileBridgeStreamSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileBridge/MobileBridgeStreamSupport.swift; sourceTree = "<group>"; };
MOBB0006 /* MobileBridgeListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileBridge/MobileBridgeListener.swift; sourceTree = "<group>"; };
MOBB0008 /* MobileBridgeSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileBridge/MobileBridgeSession.swift; sourceTree = "<group>"; };
MOBB0010 /* MobileBridgePush.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MobileBridge/MobileBridgePush.swift; sourceTree = "<group>"; };
A5001661 /* JSONCParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONCParser.swift; sourceTree = "<group>"; };
A5001641 /* RemoteRelayZshBootstrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteRelayZshBootstrap.swift; sourceTree = "<group>"; };
818DBCD4AB69EB72573E8138 /* SidebarResizeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarResizeUITests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1015,6 +1017,7 @@
MOBB0004 /* MobileBridgeStreamSupport.swift */,
MOBB0006 /* MobileBridgeListener.swift */,
MOBB0008 /* MobileBridgeSession.swift */,
MOBB0010 /* MobileBridgePush.swift */,
RVPN00000000000000000001 /* ReviewComment.swift */,
RVPN00000000000000000003 /* ReviewCommentSerializer.swift */,
RVPN00000000000000000005 /* ReviewDiffParser.swift */,
Expand Down Expand Up @@ -1500,6 +1503,7 @@
MOBB0003 /* MobileBridgeStreamSupport.swift in Sources */,
MOBB0005 /* MobileBridgeListener.swift in Sources */,
MOBB0007 /* MobileBridgeSession.swift in Sources */,
MOBB0009 /* MobileBridgePush.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Loading
Loading