Add remote machine monitoring and T3 Connect integration - #7
Conversation
- 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
📝 WalkthroughWalkthroughThis 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. ChangesRemote environment support
Multi-environment application flow
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
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
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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 20
🧹 Nitpick comments (18)
Sources/T3Notch/AppDelegate.swift (1)
87-96: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valuePath updates are compared/written across an unordered
Taskhop.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, leavingnetworkWasSatisfiedstale (missed or spurioushandleConnectivityRestored()). 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() + } + }(
networkWasSatisfiedthen 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 winOnly "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()racingrefreshT3Connect()).♻️ 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 winUse one sheet state source for the settings sheets.
MachineSettingsCardattaches three separate.sheet(isPresented:)modifiers directly to the sameSettingsCardview. 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 valueRemove the orphaned
connectionTitlecomputed property.The only
connectionTitlereference was in the removedConnectioncard, 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 valueUse a non-lazy row layout for the group threads.
AgentStore.MachineThreadGroupalready conforms toIdentifiable, so theForEach(group.threads)is safe. However, this fixed-size panel only lays out one row of thread cards, soLazyVGridadds no real benefit and can make the.asymmetricinsertion/removal transitions non-deterministic for cards that are not realized. Replace theLazyVGridwith a plainVStackof anHStack/Gridso 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 valuePin the provenance of these upstream Clerk/Electron versions.
2026-05-12,6.25.7, and0.0.18mirror 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 valueMigration 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
environmentCredentialssurvive 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 valueSwiftLint
optional_data_string_conversionwill warn here.
String(decoding:as:)trips the configured rule; the same pattern appears informFieldsat 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
formFieldswill silently mis-parse+-encoded spaces.
URLComponents.queryItemspercent-decodes but leaves+intact, so thescopeassertions only hold while the producers encode spaces as%20. IfRemotePairing.formEncodedever switches to+, this returnsorchestration:read+orchestration:operateand the failure will look like a scope bug. Replacing+with a space before parsing removes the trap. Also note the SwiftLintoptional_data_string_conversionwarning 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 winComment 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 firstlistEnvironments()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 winIdle/error detail waits bypass
sleepInterruptible.Lines 252 and 267 call
configuration.sleepdirectly, so an injected sleep hook that returns immediately (as inTests/T3NotchCoreTests/RemoteSupportTests.swiftlines 471-478) turns these branches into a tight CPU-bound spin. Routing them throughsleepInterruptiblekeeps 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 valueBoth 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
attemptConnectFallbackandrepairT3ConnectEnvironmentare near-duplicates.They share the same guard, in-flight bookkeeping, inventory refresh, lookup, and
unauthorizedhandling; only thereplacingDirectPathderivation 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 winAvoid yielding to the event stream while holding the unfair lock.
emit(and thuscontinuation.yield) plustransport.requestImmediatePoll()(which takes the transport's own lock) run insidestate.withLock.OSAllocatedUnfairLockis 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 bystop()andremove(_: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 winSynthesized
Codableconformance bypasses the validating initializer.
init(httpBaseURL:)enforces scheme/host/credential rules, but the compiler-synthesizedinit(from:)decodeshttpBaseURLdirectly. Any endpoint restored fromEnvironmentProfileStore(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)returnsnil, 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/removeare not atomic despite the lock.Both take the lock twice (once in
load, once insave) 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 theNSLock+@unchecked Sendablesurface 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 ofload()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 winMap token decoding failures to
.malformedResponse.A malformed token payload surfaces a raw
DecodingErrorto the pairing sheet (errorMessage = error.localizedDescriptioninMachineSettingsView.submit()), which is not user-presentable, while.malformedResponseexists 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 winNormalize
htubefore adding it to the DPoP payload
components.url?.absoluteStringpreserves the host casing and explicit default port, so proofs carry non-normalized values likehttps://MINI.example:443/oauth/token. Normalizing the effective request URI per RFC 3986/RFC 9449 guidance — lowercasinghttps/host and removing default ports before comparison — avoids rejection by stricter servers. Update the existing DPoP test assertion to expect the normalizedhtu.🤖 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
📒 Files selected for processing (22)
Package.swiftREADME.mdScripts/bundle.shSources/CCommonCrypto/module.modulemapSources/CCommonCrypto/shim.hSources/T3Notch/AgentStore.swiftSources/T3Notch/AppDelegate.swiftSources/T3Notch/MachineSettingsView.swiftSources/T3Notch/NotchViews.swiftSources/T3Notch/SettingsView.swiftSources/T3NotchCore/DPoP.swiftSources/T3NotchCore/ElectronSafeStorageImporter.swiftSources/T3NotchCore/EnvironmentProfileStore.swiftSources/T3NotchCore/MultiEnvironmentCoordinator.swiftSources/T3NotchCore/RemoteCredentialVault.swiftSources/T3NotchCore/RemoteModels.swiftSources/T3NotchCore/RemotePairing.swiftSources/T3NotchCore/ServerDiscovery.swiftSources/T3NotchCore/T3Connect.swiftSources/T3NotchCore/T3HTTPClient.swiftSources/T3NotchCore/T3Transport.swiftTests/T3NotchCoreTests/RemoteSupportTests.swift
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
🔒 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/nullRepository: 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/**' . || trueRepository: 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.swiftRepository: 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:
- 1: pingdotgg/t3code@5ae77c0
- 2: [codex] align relay foundation Effect services pingdotgg/t3code#3182
- 3: Migrate desktop auth to Clerk bridge pingdotgg/t3code#3092
- 4: https://www.example-code.com/android/dpop_token_header.asp
- 5: https://community.auth0.com/t/protect-your-access-tokens-with-dpop-demonstrating-proof-of-possession/191057
- 6: [codex] Enrich relay authorization diagnostics pingdotgg/t3code#2977
- 7: https://github.com/pingdotgg/t3code/blob/31b52acc8d230f66034f8dc1163cf6b4502ba77e/REMOTE.md
- 8: https://github.com/pingdotgg/t3code/blob/main/docs/user/remote-access.md
🌐 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:
- 1: https://github.com/pingdotgg/t3code
- 2: https://betterstack.com/community/guides/ai/t3-code/
- 3: https://github.com/pingdotgg/t3code/blob/main/packages/client-runtime/README.md
- 4: https://pingdotgg-t3code.mintlify.app/introduction
- 5: https://pingdotgg-t3code.mintlify.app/concepts/architecture
- 6: [codex] align relay foundation Effect services pingdotgg/t3code#3182
- 7: https://github.com/pingdotgg/t3code/blob/main/packages/client-runtime/src/authorization/service.ts
- 8: https://github.com/pingdotgg/t3code/blob/main/packages/client-runtime/src/authorization/remote.ts
- 9: https://github.com/pingdotgg/t3code/blob/main/docs/user/remote-access.md
- 10: https://github.com/pingdotgg/t3code/blob/91a03e074751e9dc732d0dddcd7b3a291caba34f/REMOTE.md
- 11: https://mintlify.wiki/pingdotgg/t3code/configuration/environment-variables
- 12: https://pingdotgg-t3code.mintlify.app/api/server/configuration
🌐 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:
- 1: https://github.com/pingdotgg/t3code
- 2: https://github.com/pingdotgg
- 3: https://github.com/pingdotgg/t3code/blob/main/packages/client-runtime/README.md
- 4: https://github.com/pingdotgg/t3code/tree/main/packages/client-runtime
- 5: Prepare datamodel for multi-environment pingdotgg/t3code#1765
🌐 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:
- 1: https://mintlify.wiki/pingdotgg/t3code/api/websocket-protocol
- 2: https://pingdotgg-t3code.mintlify.app/api/orchestration
- 3: https://mintlify.wiki/pingdotgg/t3code/api/commands
- 4: https://github.com/tarik02/t3code-cli/blob/master/skills/t3code-cli/reference/commands.md
- 5: Prepare datamodel for multi-environment pingdotgg/t3code#1765
- 6: https://github.com/tarik02/t3code-cli/blob/master/skills/t3code-cli/reference/setup.md
- 7: https://github.com/pingdotgg/t3code/blob/main/packages/contracts/src/environment.ts
- 8: https://pingdotgg-t3code.mintlify.app/concepts/architecture
🌐 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:
- 1: Prepare datamodel for multi-environment pingdotgg/t3code#1765
- 2: https://github.com/pingdotgg/t3code/blob/main/packages/client-runtime/src/authorization/remote.ts
- 3: https://github.com/pingdotgg/t3code/blob/main/packages/client-runtime/src/authorization/service.ts
- 4: https://github.com/boblangley/t3code-devcontainer-relay/blob/main/module/API.md
- 5: Use Effect schema decoders for JSON parsing pingdotgg/t3code#3060
- 6: [codex] fix: guard DPoP fallback URL construction pingdotgg/t3code#3503
- 7: https://fusionauth.io/docs/lifecycle/authenticate-users/oauth/dpop
- 8: https://tus.auth0.com/docs/secure/sender-constraining/demonstrating-proof-of-possession-dpop
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.
- 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
There was a problem hiding this comment.
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
📒 Files selected for processing (17)
Sources/T3Notch/AgentStore.swiftSources/T3Notch/AppDelegate.swiftSources/T3Notch/MachineSettingsView.swiftSources/T3Notch/NotchViews.swiftSources/T3Notch/SettingsView.swiftSources/T3NotchCore/DPoP.swiftSources/T3NotchCore/ElectronSafeStorageImporter.swiftSources/T3NotchCore/EnvironmentProfileStore.swiftSources/T3NotchCore/FormURLEncoding.swiftSources/T3NotchCore/MultiEnvironmentCoordinator.swiftSources/T3NotchCore/RemoteCredentialVault.swiftSources/T3NotchCore/RemotePairing.swiftSources/T3NotchCore/ServerDiscovery.swiftSources/T3NotchCore/T3Connect.swiftSources/T3NotchCore/T3HTTPClient.swiftSources/T3NotchCore/T3Transport.swiftTests/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
- 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
There was a problem hiding this comment.
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 winKeep the success fixture’s bootstrap credential unexpired.
expiresAtis 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 winAssert against the credential actually passed.
Line 512 checks
single-use-secret, but the test passessingle+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
📒 Files selected for processing (3)
Sources/T3NotchCore/FormURLEncoding.swiftSources/T3NotchCore/MultiEnvironmentCoordinator.swiftTests/T3NotchCoreTests/RemoteSupportTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
- Sources/T3NotchCore/MultiEnvironmentCoordinator.swift
Summary
Testing
RemoteSupportTests.swift.Summary by CodeRabbit