Skip to content

Add remote machine monitoring and T3 Connect integration - #7

Merged
zortos293 merged 3 commits into
mainfrom
feature/remote-machine-monitoring
Jul 28, 2026
Merged

Add remote machine monitoring and T3 Connect integration#7
zortos293 merged 3 commits into
mainfrom
feature/remote-machine-monitoring

Conversation

@zortos293

@zortos293 zortos293 commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add concurrent monitoring for local and remote T3 Code environments.
  • Support secure direct pairing over LAN, Tailscale, and HTTPS with DPoP credentials.
  • Add T3 Connect compatibility import, environment discovery, and fallback access.
  • Add machine management UI, grouped agent views, connection recovery, and deep links.
  • Expand documentation and add CommonCrypto integration for remote authentication.

Testing

  • Added comprehensive remote support coverage in RemoteSupportTests.swift.
  • Not run: full test suite and macOS UI/integration validation.

Summary by CodeRabbit

  • New Features
    • Added support for managing multiple local and remote machines concurrently, with enhanced remote machine visibility and maintenance.
    • Introduced direct remote pairing with optional T3 Connect integration, including importing and credential unlock/permission flows.
    • Added a new Machines settings UI for adding/removing machines, toggling environments, reconnecting, and handling remote credential issues.
    • Improved connection behavior using connectivity monitoring, smarter polling/backoff, and clearer offline/unauthorized states.
    • Updated the thread deck to group active threads by machine/project and improve completed-thread dismissal and animations.
  • Documentation
    • Expanded remote setup and connection behavior documentation, including detailed T3 Connect pairing/import workflows.

- Pair with remote environments using DPoP-secured credentials
- Add T3 Connect imports, multi-machine coordination, and settings UI
- Document remote access, credential storage, and connection behavior
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds remote machine pairing and T3 Connect support, including DPoP authentication, credential persistence, concurrent environment coordination, adaptive polling, machine settings UI, connectivity recovery, packaging configuration, documentation, and remote-support tests.

Changes

Remote environment support

Layer / File(s) Summary
Contracts, authentication, and polling
Package.swift, Sources/CCommonCrypto/*, Sources/T3NotchCore/{ServerDiscovery,RemoteModels,DPoP,T3HTTPClient,T3Transport,FormURLEncoding}.swift
Adds scoped environment models, canonical endpoints, DPoP signing, pluggable HTTP authorization, CommonCrypto wiring, form encoding, and configurable polling/backoff behavior.
Credential import and persistence
Sources/T3NotchCore/{ElectronSafeStorageImporter,EnvironmentProfileStore,RemoteCredentialVault}.swift
Adds safe Electron session import, versioned profile storage, and Keychain-backed credential documents.
Pairing and T3 Connect
Sources/T3NotchCore/{RemotePairing,T3Connect}.swift, Scripts/bundle.sh
Implements direct pairing, Clerk and relay exchanges, environment verification, credential caching, and optional bundled T3 Connect configuration.

Multi-environment application flow

Layer / File(s) Summary
Coordinator and store integration
Sources/T3NotchCore/MultiEnvironmentCoordinator.swift, Sources/T3Notch/AgentStore.swift
Coordinates concurrent environment transports and updates AgentStore to use snapshots, scoped IDs, remote restoration, scoped dispatch, and machine grouping.
Machine settings and lifecycle
Sources/T3Notch/{MachineSettingsView,SettingsView,NotchViews,AppDelegate}.swift
Adds machine management, pairing/import sheets, connection-state controls, grouped thread cards, review handling, and network/wake refresh behavior.
Documentation and validation
README.md, Tests/T3NotchCoreTests/RemoteSupportTests.swift
Documents remote connectivity and validates pairing, DPoP, persistence, safe-storage import, polling, and T3 Connect flows.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • zortos293/T3Notch#4: Related AgentStore changes for scoped focused-thread selection and detail reset behavior.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MachineSettingsView
  participant AgentStore
  participant RemotePairingClient
  participant RemoteMachine
  User->>MachineSettingsView: enter pairing link or host and code
  MachineSettingsView->>AgentStore: start remote pairing
  AgentStore->>RemotePairingClient: pair remote target
  RemotePairingClient->>RemoteMachine: fetch descriptor and exchange credential
  RemoteMachine-->>RemotePairingClient: verified session and environment
  RemotePairingClient-->>AgentStore: return profile and access credential
  AgentStore-->>MachineSettingsView: display connected machine
Loading
sequenceDiagram
  participant AppDelegate
  participant AgentStore
  participant MultiEnvironmentCoordinator
  participant EnvironmentServer
  AppDelegate->>AgentStore: handle connectivity restored
  AgentStore->>MultiEnvironmentCoordinator: reconnect environments
  MultiEnvironmentCoordinator->>EnvironmentServer: poll each environment
  EnvironmentServer-->>MultiEnvironmentCoordinator: return snapshots
  MultiEnvironmentCoordinator-->>AgentStore: emit environment events
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.80% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change set: remote machine monitoring plus T3 Connect integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/remote-machine-monitoring

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 20

🧹 Nitpick comments (18)
Sources/T3Notch/AppDelegate.swift (1)

87-96: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Path updates are compared/written across an unordered Task hop.

Each path callback spawns a detached Task, and Swift gives no ordering guarantee between them, so a quick unsatisfied→satisfied flap can be processed out of order, leaving networkWasSatisfied stale (missed or spurious handleConnectivityRestored()). Computing the transition on the monitor queue and hopping only for the store call removes the window.

♻️ Compute transition off the main-actor hop
-        networkMonitor.pathUpdateHandler = { [weak self] path in
-            Task { `@MainActor` in
-                guard let self else { return }
-                let isSatisfied = path.status == .satisfied
-                if isSatisfied && !self.networkWasSatisfied {
-                    self.store.handleConnectivityRestored()
-                }
-                self.networkWasSatisfied = isSatisfied
-            }
-        }
+        // Serialized on `networkMonitorQueue`, so the transition test is race-free.
+        var wasSatisfied = true
+        networkMonitor.pathUpdateHandler = { [weak self] path in
+            let isSatisfied = path.status == .satisfied
+            let restored = isSatisfied && !wasSatisfied
+            wasSatisfied = isSatisfied
+            guard restored else { return }
+            Task { `@MainActor` in
+                self?.store.handleConnectivityRestored()
+            }
+        }

(networkWasSatisfied then becomes redundant.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3Notch/AppDelegate.swift` around lines 87 - 96, Update the
networkMonitor.pathUpdateHandler transition logic so path.status and the
previous satisfaction state are compared and updated on the monitor’s serial
callback queue before creating any Task. Remove the now-redundant
networkWasSatisfied state, and dispatch only the resulting connectivity-restored
store call to the main actor.
Sources/T3Notch/MachineSettingsView.swift (2)

199-227: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Only "Refresh" is gated on isRemoteOperationRunning.

"Import again…", "Forget", and the "Unlock" row (Line 47) stay tappable while a remote operation is in flight, allowing overlapping mutations of the vault/profile store (e.g. forgetT3Connect() racing refreshT3Connect()).

♻️ Gate the mutating actions too
                     PillButton("Forget") {
                         Task { await store.forgetT3Connect() }
                     }
+                    .disabled(store.isRemoteOperationRunning)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3Notch/MachineSettingsView.swift` around lines 199 - 227, Update the
T3 Connect action controls in the view’s imported-state branch, including
“Import again…” and “Forget,” to disable while store.isRemoteOperationRunning is
true, matching the existing Refresh gating. Also apply the same disabled state
to the “Unlock” row action so no mutating T3 Connect operation can be triggered
during an in-flight remote operation.

81-96: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use one sheet state source for the settings sheets.

MachineSettingsCard attaches three separate .sheet(isPresented:) modifiers directly to the same SettingsCard view. On the same hierarchy level, SwiftUI can only present one sheet at a time and does not guarantee the other bindings will publish reliably. Combine these into a single .sheet(item:) driven by an enum, or nest non-conflicting sheets deeper so each button owns its own presentation context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3Notch/MachineSettingsView.swift` around lines 81 - 96, Consolidate
the three presentation bindings in MachineSettingsView into one sheet state
source: define an identifiable enum for the pairing, connect-import, and
permission-warning destinations, then replace the separate .sheet(isPresented:)
modifiers with a single .sheet(item:) that switches over the enum and preserves
each existing sheet’s content and permission-copy behavior.
Sources/T3Notch/SettingsView.swift (1)

189-189: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the orphaned connectionTitle computed property.

The only connectionTitle reference was in the removed Connection card, so this is now dead code that should be cleaned up.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3Notch/SettingsView.swift` at line 189, Remove the unused
connectionTitle computed property from the settings view, leaving
MachineSettingsCard and the remaining settings logic unchanged.
Sources/T3Notch/NotchViews.swift (1)

262-295: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a non-lazy row layout for the group threads.

AgentStore.MachineThreadGroup already conforms to Identifiable, so the ForEach(group.threads) is safe. However, this fixed-size panel only lays out one row of thread cards, so LazyVGrid adds no real benefit and can make the .asymmetric insertion/removal transitions non-deterministic for cards that are not realized. Replace the LazyVGrid with a plain VStack of an HStack/Grid so the row transitions stay tied to the actual views.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3Notch/NotchViews.swift` around lines 262 - 295, Replace the
LazyVGrid in the group thread layout with a non-lazy single-row container, using
an HStack or Grid while preserving the existing card spacing, alignment, ForEach
over group.threads, transitions, and animation. Keep ThreadCard as the realized
view for every thread so asymmetric insertion and removal transitions remain
deterministic.
Tests/T3NotchCoreTests/RemoteSupportTests.swift (5)

611-615: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pin the provenance of these upstream Clerk/Electron versions.

2026-05-12, 6.25.7, and 0.0.18 mirror T3 Code's client contract; without a pointer to where they came from, the next person seeing a failure here cannot tell whether the app or the upstream contract moved. A one-line comment naming the upstream source keeps this maintainable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift` around lines 611 - 615,
Above the version assertions in the remote support test, add a one-line comment
identifying the upstream T3 Code client contract as the source for the pinned
Clerk API, Clerk JS, and Electron SDK versions. Keep the existing assertions and
values unchanged.

277-303: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Migration half is untested.

The test decodes a v1 document and checks defaults, but never re-encodes to assert the version is upgraded and that legacy environmentCredentials survive a round trip — which is what "migrates" implies.

♻️ Suggested addition
         `#expect`(document.connectEnvironmentCredentials["mini"]?.accessToken == "connect")
+        let round = try JSONDecoder().decode(
+            RemoteCredentialDocument.self,
+            from: try JSONEncoder().encode(document)
+        )
+        `#expect`(round.version == RemoteCredentialDocument().version)
+        `#expect`(round.environmentCredentials["mini"]?.accessToken == "direct")
+        `#expect`(round.connectEnvironmentCredentials["mini"]?.accessToken == "connect")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift` around lines 277 - 303,
Extend credentialDocumentMigratesAndSeparatesDirectFromConnectTokens to
re-encode the decoded v1 document and decode it again, asserting the persisted
version is upgraded and the legacy environmentCredentials entry remains intact
after the round trip. Keep the existing separation assertions for direct and
connect tokens unchanged.

269-272: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

SwiftLint optional_data_string_conversion will warn here.

String(decoding:as:) trips the configured rule; the same pattern appears in formFields at line 844. Either switch to the failable initializer or disable the rule for the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift` around lines 269 - 272, The
serialized data conversion in the affected test and the matching formFields
conversion violates the configured optional_data_string_conversion rule. Update
both String conversions to use the failable data initializer, or apply a
file-level SwiftLint disable for this rule if retaining the current conversions.

Source: Linters/SAST tools


843-850: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

formFields will silently mis-parse +-encoded spaces.

URLComponents.queryItems percent-decodes but leaves + intact, so the scope assertions only hold while the producers encode spaces as %20. If RemotePairing.formEncoded ever switches to +, this returns orchestration:read+orchestration:operate and the failure will look like a scope bug. Replacing + with a space before parsing removes the trap. Also note the SwiftLint optional_data_string_conversion warning on line 844, same as line 270.

♻️ Suggested hardening
 private func formFields(_ data: Data) -> [String: String] {
-    let components = URLComponents(string: "?\(String(decoding: data, as: UTF8.self))")
+    let raw = String(decoding: data, as: UTF8.self)
+        .replacingOccurrences(of: "+", with: "%20")
+    let components = URLComponents(string: "?\(raw)")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift` around lines 843 - 850,
Update formFields to normalize form-encoded plus signs to spaces before
constructing URLComponents, so queryItems handles both + and %20 space encoding
consistently. Also replace the String(decoding: data, as: UTF8.self) conversion
with the project-approved optional Data-to-String conversion to avoid the
SwiftLint optional_data_string_conversion warning.

Source: Linters/SAST tools


669-705: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Comment at line 699 describes something the test does not do, and the counter-keyed handler is brittle.

Nothing is "replaced" — the 401 comes from mode.increment() returning >1, which silently assumes the first listEnvironments() issues exactly one request. Key the handler on path plus an explicit phase flag so the intent survives a change in request ordering.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift` around lines 669 - 705,
Update the listEnvironments test handler to use an explicit phase flag keyed by
the request path instead of relying on mode.increment() and request count. Set
the phase after the first scenario completes, adjust the response selection
accordingly, and replace the misleading “Replace the first response” comment
with wording that reflects transitioning to the Clerk 401 phase.
Sources/T3NotchCore/T3Transport.swift (2)

249-296: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Idle/error detail waits bypass sleepInterruptible.

Lines 252 and 267 call configuration.sleep directly, so an injected sleep hook that returns immediately (as in Tests/T3NotchCoreTests/RemoteSupportTests.swift lines 471-478) turns these branches into a tight CPU-bound spin. Routing them through sleepInterruptible keeps the cancellation/force-poll checks and bounds the spin.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/T3Transport.swift` around lines 249 - 296, Route the
idle-detail wait and error wait in the detail polling loop through
sleepInterruptible instead of calling configuration.sleep directly. Update both
branches around focused-thread filtering and the fetchThreadDetail catch block,
preserving their existing idleDetailNanoseconds duration while retaining
cancellation and force-poll checks.

209-235: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Both catch clauses repeat the same backoff block.

The four-statement backoff/jitter/notify/sleep sequence is duplicated verbatim; only the connection-state decision differs. Extracting it keeps the two paths from drifting.

♻️ Extract the backoff step
-            } catch let error as T3HTTPError {
-                if case .unauthorized = error {
-                    setConnectionState(.unauthorized)
-                } else {
-                    if !setConnectionState(.disconnected) {
-                        onRepeatedFailure?()
-                    }
-                }
-                backoffNanos = min(
-                    max(backoffNanos * 2, 500_000_000),
-                    configuration.maximumBackoffNanoseconds
-                )
-                let delay = configuration.jitter(backoffNanos)
-                configuration.onBackoff(delay)
-                await sleepInterruptible(nanoseconds: delay)
-            } catch {
-                if !setConnectionState(.disconnected) {
-                    onRepeatedFailure?()
-                }
-                backoffNanos = min(
-                    max(backoffNanos * 2, 500_000_000),
-                    configuration.maximumBackoffNanoseconds
-                )
-                let delay = configuration.jitter(backoffNanos)
-                configuration.onBackoff(delay)
-                await sleepInterruptible(nanoseconds: delay)
-            }
+            } catch {
+                if let http = error as? T3HTTPError, case .unauthorized = http {
+                    setConnectionState(.unauthorized)
+                } else if !setConnectionState(.disconnected) {
+                    onRepeatedFailure?()
+                }
+                await backOff(&backoffNanos)
+            }

with

private func backOff(_ backoffNanos: inout UInt64) async {
    backoffNanos = min(
        max(backoffNanos * 2, 500_000_000),
        configuration.maximumBackoffNanoseconds
    )
    let delay = configuration.jitter(backoffNanos)
    configuration.onBackoff(delay)
    await sleepInterruptible(nanoseconds: delay)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/T3Transport.swift` around lines 209 - 235, Extract the
duplicated backoff, jitter, notification, and interruptible-sleep sequence from
both catch clauses into a private async backOff method that accepts backoffNanos
inout and uses configuration and sleepInterruptible. Replace both inline blocks
with calls to this helper while preserving each catch clause’s existing
connection-state handling.
Sources/T3Notch/AgentStore.swift (1)

1183-1243: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

attemptConnectFallback and repairT3ConnectEnvironment are near-duplicates.

They share the same guard, in-flight bookkeeping, inventory refresh, lookup, and unauthorized handling; only the replacingDirectPath derivation and the counter resets differ. Collapsing them into one helper with a parameter keeps the two recovery paths from drifting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3Notch/AgentStore.swift` around lines 1183 - 1243, Consolidate
attemptConnectFallback and repairT3ConnectEnvironment into one shared recovery
helper parameterized by the replacingDirectPath derivation and whether
successful fallback counters must be reset. Keep the existing guard, in-flight
bookkeeping, environment refresh and lookup, connection call, unauthorized
handling, and path-specific counter behavior unchanged; have both callers
delegate to the helper.
Sources/T3NotchCore/MultiEnvironmentCoordinator.swift (1)

143-151: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid yielding to the event stream while holding the unfair lock.

emit (and thus continuation.yield) plus transport.requestImmediatePoll() (which takes the transport's own lock) run inside state.withLock. OSAllocatedUnfairLock is not reentrant and should not span arbitrary callee code. Collect the sessions under the lock, then mutate/emit outside it — the same shape already used by stop() and remove(_:emitEvent:).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/MultiEnvironmentCoordinator.swift` around lines 143 -
151, Update reconnect(_:) to collect the matching sessions while holding state’s
lock, then set their connecting state, request immediate polls, and call emit
outside the lock. Preserve the environmentID filtering and follow the
lock-release-then-process structure used by stop() and remove(_:emitEvent:).
Sources/T3NotchCore/ServerDiscovery.swift (1)

17-41: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Synthesized Codable conformance bypasses the validating initializer.

init(httpBaseURL:) enforces scheme/host/credential rules, but the compiler-synthesized init(from:) decodes httpBaseURL directly. Any endpoint restored from EnvironmentProfileStore (UserDefaults) or a decoded relay payload can therefore carry a non-HTTP scheme, embedded credentials, or a query string, defeating these checks at exactly the layer that persists them.

♻️ Route decoding through the validating initializer
 public struct ServerEndpoint: Codable, Sendable, Equatable, Hashable {
     public let httpBaseURL: URL
+
+    public init(from decoder: any Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        try self.init(httpBaseURL: container.decode(URL.self, forKey: .httpBaseURL))
+    }
 
     public init(httpBaseURL: URL) throws {

Also note line 29-32: when URLComponents(url:resolvingAgainstBaseURL:false) returns nil, the credential guard silently passes and the failure surfaces later as .missingHost.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/ServerDiscovery.swift` around lines 17 - 41, Replace the
synthesized Codable decoding for ServerEndpoint with a custom init(from:) that
decodes the URL and routes it through init(httpBaseURL:), preserving all scheme,
host, credential, and canonicalization validation for persisted or relay-decoded
values. Also explicitly handle a nil URLComponents result in init(httpBaseURL:)
rather than allowing the credential guard to pass and reporting the later
.missingHost error.
Sources/T3NotchCore/EnvironmentProfileStore.swift (1)

40-52: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

upsert/remove are not atomic despite the lock.

Both take the lock twice (once in load, once in save) with a gap in between, so two concurrent mutations can lose one another's write. Today's callers appear to be @MainActor (AgentStore), so this is latent rather than live, but the NSLock + @unchecked Sendable surface implies thread-safety it does not provide.

♻️ Hold the lock across the read-modify-write
+    private func mutate(_ transform: ([EnvironmentProfile]) -> [EnvironmentProfile]) throws {
+        try lock.withLock {
+            let current = decodeLocked()
+            let data = try JSONEncoder().encode(Document(profiles: transform(current)))
+            defaults.set(data, forKey: key)
+        }
+    }
+
     public func upsert(_ profile: EnvironmentProfile) throws {
-        var profiles = load()
-        if let index = profiles.firstIndex(where: { $0.environmentID == profile.environmentID }) {
-            profiles[index] = profile
-        } else {
-            profiles.append(profile)
-        }
-        try save(profiles)
+        try mutate { profiles in
+            var profiles = profiles
+            if let index = profiles.firstIndex(
+                where: { $0.environmentID == profile.environmentID }
+            ) {
+                profiles[index] = profile
+            } else {
+                profiles.append(profile)
+            }
+            return profiles
+        }
     }
 
     public func remove(_ environmentID: EnvironmentID) throws {
-        try save(load().filter { $0.environmentID != environmentID })
+        try mutate { $0.filter { $0.environmentID != environmentID } }
     }

(decodeLocked() being the body of load() without the lock, shared by both.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/EnvironmentProfileStore.swift` around lines 40 - 52, Make
EnvironmentProfileStore.upsert and remove atomic by acquiring the store’s NSLock
once around the complete read-modify-write operation, using a shared
decodeLocked() helper for the unlocked load logic and saving while the same lock
remains held. Avoid calling load() or save() implementations that reacquire the
lock from these mutation methods, while preserving their existing profile
replacement, append, and filtering behavior.
Sources/T3NotchCore/RemotePairing.swift (1)

198-204: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Map token decoding failures to .malformedResponse.

A malformed token payload surfaces a raw DecodingError to the pairing sheet (errorMessage = error.localizedDescription in MachineSettingsView.submit()), which is not user-presentable, while .malformedResponse exists for exactly this case.

♻️ Proposed change
-        let token = try JSONDecoder().decode(TokenResponse.self, from: data)
+        guard let token = try? JSONDecoder().decode(TokenResponse.self, from: data) else {
+            throw RemotePairingError.malformedResponse
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/RemotePairing.swift` around lines 198 - 204, Update the
token decoding flow around TokenResponse in RemotePairing so any JSONDecoder
decoding failure is caught and mapped to RemotePairingError.malformedResponse,
while preserving the existing unexpectedTokenType and unexpectedScopes
validation errors.
Sources/T3NotchCore/DPoP.swift (1)

75-92: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Normalize htu before adding it to the DPoP payload

components.url?.absoluteString preserves the host casing and explicit default port, so proofs carry non-normalized values like https://MINI.example:443/oauth/token. Normalizing the effective request URI per RFC 3986/RFC 9449 guidance — lowercasing https/host and removing default ports before comparison — avoids rejection by stricter servers. Update the existing DPoP test assertion to expect the normalized htu.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/DPoP.swift` around lines 75 - 92, The DPoP payload
currently uses components.url?.absoluteString without normalizing the effective
request URI. Update the URL normalization in the DPoP proof-building method
around DPoPPayload to lowercase the HTTPS scheme and host and remove the default
HTTPS port before assigning htu; retain query and fragment removal, and update
the existing DPoP test assertion to expect the normalized URI.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/T3Notch/AgentStore.swift`:
- Around line 668-671: Update the credential freshness guard in the surrounding
AgentStore flow to use !credential.needsRefresh instead of directly comparing
credential.expiresAt with .now, matching the Connect path and rejecting
credentials already within the refresh window before installation.
- Around line 1502-1509: In the event-consumption flow around
applyEnvironmentEvent, detach the potentially slow updateAccessPathHealth and
repairT3ConnectEnvironment recovery work into an independent Task so
observeCoordinator continues processing snapshots and detail events without
awaiting network round-trips. Preserve the existing T3 Connect unauthorized
condition and keep rebuildFlattenedWorld/applyCombinedShell execution in the
event loop.
- Around line 593-702: Update restoreRemoteMachines to prevent overlapping
executions with an in-flight guard, returning or otherwise preserving the
existing restore when one is already active; clear the guard on every completion
path. Refactor the enabled-profile restoration work into a task group so
fetchEnvironment and verifySession operations run concurrently while
coordinator.register and placeholder updates remain safe, preserving existing
per-profile states and final configureT3Connect/attemptConnectFallback behavior.
- Around line 94-101: Move T3 Connect configuration and detection work out of
the main-thread init and maintenance paths by performing refreshes
asynchronously off the main actor, then publish the resulting cached state on
the main actor. Update canImportT3Connect, showsT3Connect,
t3ConnectImportDetail, and isT3ConnectEnvironmentEnabled to read cached
observable values instead of reloading configuration, UserDefaults, filesystem
data, or remoteVault on each computed-property access; ensure
refreshT3ConnectDetection and performRemoteMaintenance preserve the cached-state
update behavior.
- Around line 1544-1551: Update the previousByID and incomingByID construction
in the retained-completions flow to tolerate duplicate server-supplied thread
IDs instead of using Dictionary(uniqueKeysWithValues:). Build each dictionary
with an explicit duplicate-key resolution policy, preserving one deterministic
thread entry per ID and avoiding runtime traps for malformed remote shell
snapshots.
- Around line 1038-1055: Update the bulk connection loop in
refreshT3ConnectEnvironments to isolate failures from
connectT3ConnectEnvironment per environment. Catch each connection error inside
the loop, continue processing subsequent environments, and preserve the existing
skip conditions and successful connection behavior.

In `@Sources/T3Notch/AppDelegate.swift`:
- Line 300: Update the connectivity-restore flow around
handleConnectivityRestored() to call bootstrap() when connectionState is
.unauthorized or needsOnboarding is true; otherwise retain the existing
handleConnectivityRestored() behavior for normal reconnects.

In `@Sources/T3Notch/MachineSettingsView.swift`:
- Line 126: Update the descriptor label expression in MachineSettingsView to use
a helper that exists in the repository, or implement the needed empty/blank
check inline, while preserving the fallback to machine.profile.label when the
descriptor label has no usable value.

In `@Sources/T3Notch/NotchViews.swift`:
- Around line 319-325: Update the completed-card interaction around markReviewed
and selectThread so the card’s visible label or accessibility metadata clearly
announces that tapping a completed card will dismiss it, while preserving
selection behavior for non-completed cards.

In `@Sources/T3NotchCore/ElectronSafeStorageImporter.swift`:
- Around line 63-78: Update detect() to validate the encrypted record’s required
enc: prefix and v10 magic before returning .signedIn, matching importSession()
validation; classify records that fail this format check as .incompatible while
preserving existing permission, invalid-session, and valid-record handling.

In `@Sources/T3NotchCore/MultiEnvironmentCoordinator.swift`:
- Around line 110-132: Update setFocusedThread so the newly created detail task
is stored only if state.focused still equals the captured focused value when
reacquiring the lock; otherwise cancel that task immediately. Preserve
cancellation of the previously tracked detailTask and ensure stale overlapping
calls cannot leave an untracked detail stream running.
- Around line 5-25: Synchronize the shared mutable state in both affected sites:
in Sources/T3NotchCore/MultiEnvironmentCoordinator.swift lines 5-25, move
Session.profile, descriptor, state, and shell into the locked State structure or
protect them with a dedicated lock so transport callbacks, shellTask, and
makeSnapshot use synchronized access; in Sources/T3NotchCore/T3Transport.swift
lines 83-103, store onConnectionStateChange and onRepeatedFailure inside the
existing OSAllocatedUnfairLock state or provide them through initialization,
preventing the shell loop from racing with post-construction assignment.
- Around line 54-91: Update the environment re-registration flow around the
coordinator’s registration method to capture the currently focused thread for
the target environment before calling remove, then restore that focus on the
replacement session and transport so the detail stream continues without user
reselection. Remove the unreachable previous-session bookkeeping and its
stop/cancel calls, since remove already handles the old session.

In `@Sources/T3NotchCore/RemoteCredentialVault.swift`:
- Around line 122-155: Make RemoteCredentialVault.update perform the document
read, transform, and save as one serialized mutation using a dedicated mutation
lock, rather than allowing concurrent read-modify-write cycles; keep the
existing lock available for cache access to avoid re-entrancy. Remove the
unreachable errSecItemNotFound catch in update because document/load already
returns an empty RemoteCredentialDocument for that status, while preserving the
existing transform and save behavior.

In `@Sources/T3NotchCore/RemotePairing.swift`:
- Around line 257-261: Replace the URLComponents-based encoding in formEncoded
in Sources/T3NotchCore/RemotePairing.swift:257-261 with form-body encoding that
percent-encodes each field name and value using only unreserved characters,
preserving literal field separators. Apply the same change at the relay token
exchange in Sources/T3NotchCore/T3Connect.swift:449-453, preferably by
introducing and reusing one shared T3NotchCore helper so subject_token values
preserve characters such as “+” and “&” and resource URLs are encoded correctly.

In `@Sources/T3NotchCore/T3Connect.swift`:
- Around line 46-61: Update frontendURL(from:) to validate the decoded host as a
hostname rather than only rejecting slashes and emptiness: allow only hostname
characters (including dots and hyphens as appropriate), reject URL delimiters or
user-info characters such as ?, #, :, and @, and require the validated value to
round-trip unchanged through URL hostname parsing before constructing the HTTPS
URL.
- Around line 160-185: Update listEnvironments() to build its request through
authorizedRelayRequest(), using the exchanged relay access token and DPoP proof
instead of clerkTemplateToken(). Preserve the existing environments URL,
timeout, response decoding, and unauthorized cleanup behavior.

In `@Sources/T3NotchCore/T3HTTPClient.swift`:
- Around line 65-76: Update authorize(_:) to snapshot accessToken into a local
constant before awaiting signer.createProof, then use that same snapshot both
for proof creation and the Authorization header. Preserve the existing URL
validation and request header behavior.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift`:
- Around line 497-505: In the backoff sampling test loop around
backoffs.snapshot, widen the total wait budget beyond the current 600 ms so
three network round trips can complete reliably on loaded CI, then explicitly
assert that at least three samples were collected before comparing the expected
prefix so insufficient samples report as a timeout.
- Around line 196-202: Update DPoP.createProof and the T3 verifier’s htu
handling to apply RFC 9449 URI normalization before comparison, including
lowercasing the host and removing the default :443 port for HTTPS while
preserving the path. Adjust the affected test expectation and rename
dpoppayloadCarriesNormalizedClaimsAndRawES256Signature to reflect the normalized
htu behavior.

---

Nitpick comments:
In `@Sources/T3Notch/AgentStore.swift`:
- Around line 1183-1243: Consolidate attemptConnectFallback and
repairT3ConnectEnvironment into one shared recovery helper parameterized by the
replacingDirectPath derivation and whether successful fallback counters must be
reset. Keep the existing guard, in-flight bookkeeping, environment refresh and
lookup, connection call, unauthorized handling, and path-specific counter
behavior unchanged; have both callers delegate to the helper.

In `@Sources/T3Notch/AppDelegate.swift`:
- Around line 87-96: Update the networkMonitor.pathUpdateHandler transition
logic so path.status and the previous satisfaction state are compared and
updated on the monitor’s serial callback queue before creating any Task. Remove
the now-redundant networkWasSatisfied state, and dispatch only the resulting
connectivity-restored store call to the main actor.

In `@Sources/T3Notch/MachineSettingsView.swift`:
- Around line 199-227: Update the T3 Connect action controls in the view’s
imported-state branch, including “Import again…” and “Forget,” to disable while
store.isRemoteOperationRunning is true, matching the existing Refresh gating.
Also apply the same disabled state to the “Unlock” row action so no mutating T3
Connect operation can be triggered during an in-flight remote operation.
- Around line 81-96: Consolidate the three presentation bindings in
MachineSettingsView into one sheet state source: define an identifiable enum for
the pairing, connect-import, and permission-warning destinations, then replace
the separate .sheet(isPresented:) modifiers with a single .sheet(item:) that
switches over the enum and preserves each existing sheet’s content and
permission-copy behavior.

In `@Sources/T3Notch/NotchViews.swift`:
- Around line 262-295: Replace the LazyVGrid in the group thread layout with a
non-lazy single-row container, using an HStack or Grid while preserving the
existing card spacing, alignment, ForEach over group.threads, transitions, and
animation. Keep ThreadCard as the realized view for every thread so asymmetric
insertion and removal transitions remain deterministic.

In `@Sources/T3Notch/SettingsView.swift`:
- Line 189: Remove the unused connectionTitle computed property from the
settings view, leaving MachineSettingsCard and the remaining settings logic
unchanged.

In `@Sources/T3NotchCore/DPoP.swift`:
- Around line 75-92: The DPoP payload currently uses
components.url?.absoluteString without normalizing the effective request URI.
Update the URL normalization in the DPoP proof-building method around
DPoPPayload to lowercase the HTTPS scheme and host and remove the default HTTPS
port before assigning htu; retain query and fragment removal, and update the
existing DPoP test assertion to expect the normalized URI.

In `@Sources/T3NotchCore/EnvironmentProfileStore.swift`:
- Around line 40-52: Make EnvironmentProfileStore.upsert and remove atomic by
acquiring the store’s NSLock once around the complete read-modify-write
operation, using a shared decodeLocked() helper for the unlocked load logic and
saving while the same lock remains held. Avoid calling load() or save()
implementations that reacquire the lock from these mutation methods, while
preserving their existing profile replacement, append, and filtering behavior.

In `@Sources/T3NotchCore/MultiEnvironmentCoordinator.swift`:
- Around line 143-151: Update reconnect(_:) to collect the matching sessions
while holding state’s lock, then set their connecting state, request immediate
polls, and call emit outside the lock. Preserve the environmentID filtering and
follow the lock-release-then-process structure used by stop() and
remove(_:emitEvent:).

In `@Sources/T3NotchCore/RemotePairing.swift`:
- Around line 198-204: Update the token decoding flow around TokenResponse in
RemotePairing so any JSONDecoder decoding failure is caught and mapped to
RemotePairingError.malformedResponse, while preserving the existing
unexpectedTokenType and unexpectedScopes validation errors.

In `@Sources/T3NotchCore/ServerDiscovery.swift`:
- Around line 17-41: Replace the synthesized Codable decoding for ServerEndpoint
with a custom init(from:) that decodes the URL and routes it through
init(httpBaseURL:), preserving all scheme, host, credential, and
canonicalization validation for persisted or relay-decoded values. Also
explicitly handle a nil URLComponents result in init(httpBaseURL:) rather than
allowing the credential guard to pass and reporting the later .missingHost
error.

In `@Sources/T3NotchCore/T3Transport.swift`:
- Around line 249-296: Route the idle-detail wait and error wait in the detail
polling loop through sleepInterruptible instead of calling configuration.sleep
directly. Update both branches around focused-thread filtering and the
fetchThreadDetail catch block, preserving their existing idleDetailNanoseconds
duration while retaining cancellation and force-poll checks.
- Around line 209-235: Extract the duplicated backoff, jitter, notification, and
interruptible-sleep sequence from both catch clauses into a private async
backOff method that accepts backoffNanos inout and uses configuration and
sleepInterruptible. Replace both inline blocks with calls to this helper while
preserving each catch clause’s existing connection-state handling.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift`:
- Around line 611-615: Above the version assertions in the remote support test,
add a one-line comment identifying the upstream T3 Code client contract as the
source for the pinned Clerk API, Clerk JS, and Electron SDK versions. Keep the
existing assertions and values unchanged.
- Around line 277-303: Extend
credentialDocumentMigratesAndSeparatesDirectFromConnectTokens to re-encode the
decoded v1 document and decode it again, asserting the persisted version is
upgraded and the legacy environmentCredentials entry remains intact after the
round trip. Keep the existing separation assertions for direct and connect
tokens unchanged.
- Around line 269-272: The serialized data conversion in the affected test and
the matching formFields conversion violates the configured
optional_data_string_conversion rule. Update both String conversions to use the
failable data initializer, or apply a file-level SwiftLint disable for this rule
if retaining the current conversions.
- Around line 843-850: Update formFields to normalize form-encoded plus signs to
spaces before constructing URLComponents, so queryItems handles both + and %20
space encoding consistently. Also replace the String(decoding: data, as:
UTF8.self) conversion with the project-approved optional Data-to-String
conversion to avoid the SwiftLint optional_data_string_conversion warning.
- Around line 669-705: Update the listEnvironments test handler to use an
explicit phase flag keyed by the request path instead of relying on
mode.increment() and request count. Set the phase after the first scenario
completes, adjust the response selection accordingly, and replace the misleading
“Replace the first response” comment with wording that reflects transitioning to
the Clerk 401 phase.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 43d1d0c6-9de9-4962-bb23-f112b3b86e87

📥 Commits

Reviewing files that changed from the base of the PR and between 53a1c71 and dcbe64e.

📒 Files selected for processing (22)
  • Package.swift
  • README.md
  • Scripts/bundle.sh
  • Sources/CCommonCrypto/module.modulemap
  • Sources/CCommonCrypto/shim.h
  • Sources/T3Notch/AgentStore.swift
  • Sources/T3Notch/AppDelegate.swift
  • Sources/T3Notch/MachineSettingsView.swift
  • Sources/T3Notch/NotchViews.swift
  • Sources/T3Notch/SettingsView.swift
  • Sources/T3NotchCore/DPoP.swift
  • Sources/T3NotchCore/ElectronSafeStorageImporter.swift
  • Sources/T3NotchCore/EnvironmentProfileStore.swift
  • Sources/T3NotchCore/MultiEnvironmentCoordinator.swift
  • Sources/T3NotchCore/RemoteCredentialVault.swift
  • Sources/T3NotchCore/RemoteModels.swift
  • Sources/T3NotchCore/RemotePairing.swift
  • Sources/T3NotchCore/ServerDiscovery.swift
  • Sources/T3NotchCore/T3Connect.swift
  • Sources/T3NotchCore/T3HTTPClient.swift
  • Sources/T3NotchCore/T3Transport.swift
  • Tests/T3NotchCoreTests/RemoteSupportTests.swift

Comment thread Sources/T3Notch/AgentStore.swift
Comment thread Sources/T3Notch/AgentStore.swift
Comment thread Sources/T3Notch/AgentStore.swift Outdated
Comment thread Sources/T3Notch/AgentStore.swift
Comment thread Sources/T3Notch/AgentStore.swift Outdated
Comment thread Sources/T3NotchCore/T3Connect.swift
Comment on lines +160 to +185
public func listEnvironments() async throws -> [T3ConnectEnvironment] {
do {
let clerkToken = try await clerkTemplateToken()
var request = URLRequest(
url: configuration.relayURL.appendingPathComponent("v1/environments")
)
request.timeoutInterval = 12
request.setValue("Bearer \(clerkToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let data = try await perform(request)
let response = try JSONDecoder().decode(RelayEnvironmentList.self, from: data)
return response.environments.map { record in
let endpoint = URL(string: record.endpoint.httpBaseURL)
.flatMap { try? ServerEndpoint(httpBaseURL: $0) }
return T3ConnectEnvironment(
environmentID: EnvironmentID(record.environmentID),
label: record.label,
endpoint: endpoint,
linkedAt: record.linkedAt
)
}
} catch T3ConnectError.unauthorized {
try? vault.forgetT3Connect()
throw T3ConnectError.unauthorized
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for other relay callers / docs describing the /v1/environments auth scheme.
rg -n -C3 'v1/environments' --glob '!**/.build/**'
rg -n -C3 'dpop-token|relay' README.md 2>/dev/null

Repository: zortos293/T3Notch

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -u

echo "== files matching T3Connect =="
fd -a 'T3Connect\.swift|T3ConnectEnvironment|RelayEnvironmentList' . || true

echo "== git ls relevant =="
git ls-files | grep -E '(^|/)T3Connect\.swift$|([^/]+/)?(.*)?(relay|environment|dpop|readme|README)' | head -200 || true

echo "== outline T3Connect if present =="
file="$(fd 'T3Connect\.swift' . | head -1)"
if [ -n "$file" ]; then
  wc -l "$file"
  ast-grep outline "$file" --view compact || true
  echo "== T3Connect relevant sections =="
  sed -n '1,260p' "$file" | cat -n
fi

echo "== search authorization/relay/token helpers =="
rg -n -C3 'clerkTemplateToken|authorizedRelayRequest|dpop|Bearer|rela|EnvironmentList|v1/environment' --glob '!**/.build/**' . || true

Repository: zortos293/T3Notch

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -u

echo "== T3Connect.swift authorization/token/request sections =="
sed -n '260,490p' Sources/T3NotchCore/T3Connect.swift | cat -n

echo "== perform/list test expectations around /v1/environments =="
sed -n '716,736p' Tests/T3NotchCoreTests/RemoteSupportTests.swift | cat -n

echo "== precise mock request expectations and assertions =="
rg -n -C2 'listEnvironments|/v1/environments"' Tests/T3NotchCoreTests/RemoteSupportTests.swift
rg -n -C2 'test.*list|listEnvironments' Tests/T3NotchCoreTests/RemoteSupportTests.swift

Repository: zortos293/T3Notch

Length of output: 14858


🌐 Web query:

T3 Code relay v1/environments authorization environment:list DPoP Relay token

💡 Result:

In the T3 Code ecosystem, environment authorization and relay token management are handled through a dedicated relay infrastructure [1][2]. The T3 Code relay system employs DPoP (Demonstrating Proof of Possession) to secure environment connections [1][3]. DPoP is an OAuth 2.0 extension that binds access tokens to a specific client’s private key, preventing token misuse [4][5]. When interacting with managed relay environments, clients are required to provide DPoP proofs—JWTs signed with a private key—alongside their relay tokens to authenticate requests [6][1][4]. Key aspects of this authorization architecture include: 1. Relay Token Management: The infrastructure includes dedicated services for managing relay tokens and verifying DPoP proofs, ensuring that environment-specific operations are authorized [1][2]. 2. Environment Authorization: Operations such as environment listing or connecting require successful authorization. The relay system classifies authorization failures (e.g., missing proof key thumbprint, invalid environment link) with specific reasons to facilitate better diagnostics [6]. 3. Authentication Flow: Desktop and managed environments often use Bearer tokens or DPoP-bound tokens. For example, desktop clients may exchange bootstrap credentials for bearer tokens, which are then used in HTTP layers to authorize requests to the relay [3]. The environment:list operation and other relay-level commands interact with these underlying services to manage access to remote environments, often requiring valid authentication headers (e.g., Authorization: DPoP <access_token>) to proceed [6][4]. You can manage and inspect active sessions or credentials using the t3 auth CLI commands [7][8].

Citations:


🌐 Web query:

github.com/pingdotgg/t3code v1/environments environment:list relay accessToken

💡 Result:

The query "v1/environments environment:list relay accessToken" refers to internal components of the t3code project [1][2], specifically within the client runtime [3]. T3 Code is an open-source web GUI for AI coding agents (such as Codex, Claude, Cursor, and OpenCode) that manages sessions and provides a persistent interface for these tools [4][5][2]. The project uses a monorepo structure where the packages/client-runtime directory manages authorization (Bearer/DPoP), environment identity, connection registry, and relay discovery [3]. Regarding the specific terms in your query: - Relay: The t3code architecture includes a relay subpath within the client runtime, which handles managed relay API interactions and environment discovery [3][6]. - Environment List & Access Tokens: The project utilizes an Effect-based service architecture for environment management [6]. Authorization involves exchanging credentials for remote DPoP (Demonstrating Proof-of-Possession) access tokens [7][8]. These tokens are persisted and used to establish secure WebSocket or HTTP communication with environments (e.g., local, LAN, or remote SSH-launched instances) [7][9][10]. There is no standard public CLI command strictly matching "v1/environments environment:list relay accessToken" for general end-users. Users typically manage environments through the application settings UI [9][10] or by running the server with specific environment variables (like T3CODE_AUTH_TOKEN) [11][12]. Advanced operations or debugging involving relay discovery and token exchange are handled internally by the client runtime's authorization and relay services [3][6]. For further investigation into the source code, you can refer to: - packages/client-runtime/src/authorization/service.ts [7] - packages/client-runtime/src/authorization/remote.ts [8] - packages/client-runtime/README.md [3]

Citations:


🌐 Web query:

pingdotgg t3code packages/client-runtime v1/environments

💡 Result:

In the context of the pingdotgg/t3code repository [1][2], the packages/client-runtime package is responsible for shared client behavior for web and mobile [3][4]. The documentation explicitly lists "environment" as a public subpath, which is responsible for "Environment identity, descriptors, endpoints, and scoped keys" [3][4]. Regarding your query for "v1/environments": While "environment" is a documented subpath for the package [3][4], there is no standard public subpath documented as "v1/environments" within the pingdotgg/t3code client-runtime package [3][4]. The package intentionally uses a flat, subpath-based API structure and avoids a root export [3][4]. Development activity in the repository, such as Pull Request #1765, highlights ongoing work on multi-environment data models, specifically introducing utilities for scoped project and thread references and environment resource identification [5]. The term "environments" is used conceptually in the project's architecture for environment discovery, identity, and supervision [3][4], but users are generally advised to import narrow, relevant subpaths like "environment" or "connection" rather than versioned directory paths like "v1/environments" [3][4].

Citations:


🌐 Web query:

"v1/environments" "environment:list" "t3code"

💡 Result:

The command "v1/environments environment:list" does not exist as a standard API method in the T3 Code ecosystem [1][2][3]. Based on the available documentation for T3 Code, environment management is handled through specific CLI tools and internal services, not a "v1/environments" endpoint [4][5][6]. In the T3 Code command-line interface (t3cli), environments are managed using the "env" command namespace [4][6]: - To list stored environments: Use the command t3cli env list [4][6]. You can optionally format the output as JSON using the --format json flag [4][6]. - Other related commands include t3cli env use <name> to switch environments and t3cli env remove to delete one [4][6]. Internally, T3 Code has moved toward a multi-environment architecture [5]. This is handled via services such as the ServerEnvironment service and contracts defined in packages/contracts/src/environment.ts, which utilize ExecutionEnvironmentDescriptor objects to track platform, version, and capability information, rather than a top-level v1 REST-style environment list endpoint [5][7]. If you are interacting with the T3 Code WebSocket API, methods are organized into namespaces such as orchestration.*, projects.*, and providers.* [1][2][8]. There is no "v1/environments" namespace documented for this API [1][3].

Citations:


🌐 Web query:

"t3code" "\"v1/environments\"" "DPoP" "/v1/environments"

💡 Result:

In the t3code project, the reference to v1/environments and DPoP relates to the authentication and management of execution environments [1]. The t3code platform uses DPoP (Demonstrating Proof-of-Possession) to secure communication between clients and remote environments [2][3]. Key points regarding this implementation: 1. DPoP Authentication: The t3code client-runtime uses DPoP to bind access tokens to specific client instances [3]. This involves sending a DPoP-signed JWT in the DPoP header along with the authorization token in the Authorization header (e.g., Authorization: DPoP <access_token>) [2]. 2. Environment Endpoints: The /v1/environments path is associated with managing and authorizing connections to these server environments [1][4]. For example, the authorizeDpop service function handles the exchange of credentials to obtain an authorized remote environment [3]. 3. Implementation Details: - The system uses Effect-based schema decoders for DPoP JWT header and payload validation [5]. - DPoP proof verification requires careful handling of request URLs to prevent security vulnerabilities, with recent updates ensuring malformed URLs are explicitly rejected rather than causing runtime errors [6]. - In certain relay or development configurations, DPoP may be bypassed or require specific patching if the client-server interaction model changes [4]. The v1/environments endpoints generally act as a management layer where clients bootstrap or authorize their connection to a specific t3code server instance [1]. The integration follows standard DPoP patterns (RFC 9449), where the proof ensures that the client possesses the private key corresponding to the public key used during the token exchange [2][7][8].

Citations:


Use the exchanged relay DPoP token for /v1/environments.

listEnvironments() sends the Clerk template JWT directly, while status and connect use authorizedRelayRequest() with the relay access token plus a DPoP proof. If relay endpoints share the same authorization model, this list call will 401 in production.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/T3NotchCore/T3Connect.swift` around lines 160 - 185, Update
listEnvironments() to build its request through authorizedRelayRequest(), using
the exchanged relay access token and DPoP proof instead of clerkTemplateToken().
Preserve the existing environments URL, timeout, response decoding, and
unauthorized cleanup behavior.

Comment thread Sources/T3NotchCore/T3HTTPClient.swift
Comment thread Tests/T3NotchCoreTests/RemoteSupportTests.swift
Comment thread Tests/T3NotchCoreTests/RemoteSupportTests.swift Outdated
- Restore remote environments concurrently with safer connection-state handling
- Improve T3 Connect detection, credential recovery, and operation guards
- Fix connectivity recovery, thread grid layout, and completed-thread accessibility

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/T3NotchCore/FormURLEncoding.swift`:
- Around line 16-23: Update FormURLEncoding’s byte-encoding logic to use
application/x-www-form-urlencoded rules: encode spaces as “+”, preserve “*”, and
percent-encode “~” while retaining the existing hexadecimal escaping for other
bytes. Add exact-body tests covering “a b” → “a+b”, “*” → “*”, and “~” → “%7E”.

In `@Sources/T3NotchCore/MultiEnvironmentCoordinator.swift`:
- Around line 143-150: Update the shell task closure around transport.shell so
self and session remain weak throughout each loop iteration rather than being
promoted by the initial guard. Use optional chaining or per-iteration weak
captures before calling session.updateShell, self.emit, and preserve the
existing early-exit behavior when either reference is unavailable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ea8a53c8-43ee-4801-ac3c-1d782a70364b

📥 Commits

Reviewing files that changed from the base of the PR and between dcbe64e and 579075e.

📒 Files selected for processing (17)
  • Sources/T3Notch/AgentStore.swift
  • Sources/T3Notch/AppDelegate.swift
  • Sources/T3Notch/MachineSettingsView.swift
  • Sources/T3Notch/NotchViews.swift
  • Sources/T3Notch/SettingsView.swift
  • Sources/T3NotchCore/DPoP.swift
  • Sources/T3NotchCore/ElectronSafeStorageImporter.swift
  • Sources/T3NotchCore/EnvironmentProfileStore.swift
  • Sources/T3NotchCore/FormURLEncoding.swift
  • Sources/T3NotchCore/MultiEnvironmentCoordinator.swift
  • Sources/T3NotchCore/RemoteCredentialVault.swift
  • Sources/T3NotchCore/RemotePairing.swift
  • Sources/T3NotchCore/ServerDiscovery.swift
  • Sources/T3NotchCore/T3Connect.swift
  • Sources/T3NotchCore/T3HTTPClient.swift
  • Sources/T3NotchCore/T3Transport.swift
  • Tests/T3NotchCoreTests/RemoteSupportTests.swift
💤 Files with no reviewable changes (1)
  • Sources/T3Notch/SettingsView.swift
🚧 Files skipped from review as they are similar to previous changes (13)
  • Sources/T3Notch/AppDelegate.swift
  • Sources/T3NotchCore/EnvironmentProfileStore.swift
  • Sources/T3NotchCore/DPoP.swift
  • Sources/T3NotchCore/ElectronSafeStorageImporter.swift
  • Tests/T3NotchCoreTests/RemoteSupportTests.swift
  • Sources/T3Notch/MachineSettingsView.swift
  • Sources/T3NotchCore/T3HTTPClient.swift
  • Sources/T3NotchCore/T3Connect.swift
  • Sources/T3NotchCore/RemotePairing.swift
  • Sources/T3NotchCore/T3Transport.swift
  • Sources/T3NotchCore/RemoteCredentialVault.swift
  • Sources/T3NotchCore/ServerDiscovery.swift
  • Sources/T3Notch/AgentStore.swift

Comment thread Sources/T3NotchCore/FormURLEncoding.swift
Comment thread Sources/T3NotchCore/MultiEnvironmentCoordinator.swift
- Apply HTML form URL-encoding rules for spaces and escaped characters
- Retain coordinator/session references while processing shell updates
- Add regression coverage for form encoding

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Tests/T3NotchCoreTests/RemoteSupportTests.swift (2)

602-604: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the success fixture’s bootstrap credential unexpired.

expiresAt is July 27, 2026, already expired as of July 28, 2026, while this test expects a successful connection. Generate a future timestamp (or inject a fixed clock) and reserve expired timestamps for a rejection test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift` around lines 602 - 604,
Update the success fixture in the relay.example response within
RemoteSupportTests so its expiresAt value is in the future relative to the
test’s current date, preserving successful connection behavior. Keep expired
timestamps only in tests that explicitly verify rejection.

511-513: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Assert against the credential actually passed.

Line 512 checks single-use-secret, but the test passes single+use&secret. This would not detect a URL leak of the real pairing code, including a percent-encoded leak. Store the input in a variable and compare each decoded request URL against that value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift` around lines 511 - 513,
Update the request assertion loop in the relevant remote-support test to store
the actual pairing credential input, `single+use&secret`, in a variable and
compare each non-token request URL after decoding against that variable. Ensure
the assertion detects both raw and percent-encoded leaks of the credential,
while continuing to exclude `/oauth/token` requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Tests/T3NotchCoreTests/RemoteSupportTests.swift`:
- Around line 602-604: Update the success fixture in the relay.example response
within RemoteSupportTests so its expiresAt value is in the future relative to
the test’s current date, preserving successful connection behavior. Keep expired
timestamps only in tests that explicitly verify rejection.
- Around line 511-513: Update the request assertion loop in the relevant
remote-support test to store the actual pairing credential input,
`single+use&secret`, in a variable and compare each non-token request URL after
decoding against that variable. Ensure the assertion detects both raw and
percent-encoded leaks of the credential, while continuing to exclude
`/oauth/token` requests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b1b1417-589d-4144-ac0e-38c38245efad

📥 Commits

Reviewing files that changed from the base of the PR and between 579075e and 68a6eab.

📒 Files selected for processing (3)
  • Sources/T3NotchCore/FormURLEncoding.swift
  • Sources/T3NotchCore/MultiEnvironmentCoordinator.swift
  • Tests/T3NotchCoreTests/RemoteSupportTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/T3NotchCore/MultiEnvironmentCoordinator.swift

@zortos293
zortos293 merged commit 1027a08 into main Jul 28, 2026
2 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.

1 participant