Skip to content

Conversation

@fredrikekelund
Copy link
Contributor

Related issues

Proposed Changes

This PR is based on @youknowriad's work in #1958. It implements a studio auth logout command that:

  1. Calls a REST API endpoint to revoke the access token
  2. Clears the access token from the appdata file

Testing Instructions

  1. Open Studio
  2. Ensure you are logged out
  3. Run npm run cli:build
  4. Run node dist/cli/main.js auth logout
  5. Ensure that an "Already logged out" message is printed
  6. Log in to WordPress.com through Studio
  7. Run node dist/cli/main.js auth logout
  8. Ensure that a "Successfully logged out" message is printed
  9. Reload Studio
  10. Ensure that you are logged out in Studio, too

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

@fredrikekelund fredrikekelund requested review from a team and bcotrim November 6, 2025 12:03
@fredrikekelund fredrikekelund self-assigned this Nov 6, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Nov 6, 2025

📊 Performance Test Results

Comparing 4465a21 vs trunk

site-editor

Metric trunk 4465a21 Diff Change
load 12002.00 ms 13228.00 ms +1226.00 ms 🔴 10.2%

site-startup

Metric trunk 4465a21 Diff Change
siteCreation 19224.00 ms 27337.00 ms +8113.00 ms 🔴 42.2%
siteStartup 6973.00 ms 10079.00 ms +3106.00 ms 🔴 44.5%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change

Copy link
Contributor

@gcsecsey gcsecsey left a comment

Choose a reason for hiding this comment

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

Thanks @fredrikekelund, this looks good to me and works well. 👍

The command's output is as described:

Logged in Logged out
Image Image

When switching back to Studio, I'm also logged out:

Image

The new tests are also all green:

Image

fredrikekelund and others added 6 commits November 17, 2025 10:54
Add comprehensive documentation for the new `studio auth login` command
including usage instructions, flow description, and important notes about
token lifetime and authentication sharing between CLI and desktop app.

Co-authored-by: Fredrik Rombach Ekelund <[email protected]>
@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Comprehensive Code Review Complete

I've completed a thorough review of the auth logout implementation. While the core functionality is good and the PR already has passing tests, I've identified some critical issues that should be addressed before merging.


🔴 Critical Issues (Must Fix)

1. Race Condition with Lock Acquisition

Location: cli/commands/auth/logout.ts:30

The lockAppdata() is called after revokeAuthToken(), creating a race condition where concurrent login/logout operations could corrupt the token state. The lock should be acquired before any mutations begin.

Impact: High - Can result in deleting a newly created token from a concurrent login operation.

2. Inconsistent Error State Handling

Location: cli/commands/auth/logout.ts:27-44

The current error handling allows the system to enter an inconsistent state:

  • If server revocation succeeds but local cleanup fails → User sees "Failed to log out" but token is actually revoked
  • If server revocation fails → No cleanup attempted (correct) but error message is generic

Impact: High - Users will be confused about their actual authentication state.


🟡 Important Issues (Should Fix)

3. API Error Handling Too Generic

Location: cli/lib/api.ts:168-179

The revokeAuthToken() function wraps all errors generically, making debugging difficult. It also doesn't handle the idempotent case where the token is already revoked (404 response should be treated as success).

Impact: Medium - Poor developer experience and user experience with error messages.

4. Missing Test Coverage

Location: cli/commands/auth/tests/logout.test.ts:97

No tests for:

  • Lock acquisition failure
  • Partial failure scenarios (server success + local failure)
  • Idempotent revocation (calling logout twice)

Impact: Medium - Edge cases are not validated.

5. Test Validates Buggy Behavior

Location: cli/commands/auth/tests/logout.test.ts:75

The test on line 75 asserts that lockAppdata is NOT called when revocation fails, which actually validates the race condition bug mentioned in issue #1.

Impact: Medium - Test will fail when the race condition is fixed.


🟢 Minor Issues (Nice to Have)

6. Redundant Method Parameter

Location: cli/lib/api.ts:174

The method: 'DELETE' parameter is redundant when using wpcom.req.del().

7. Fragile Test Assertion

Location: cli/commands/auth/tests/logout.test.ts:63

The assertion expect( saveAppdata ).toHaveBeenCalledWith( {} ) is fragile and doesn't test the actual requirement ("authToken should be removed").

8. Inconsistent Error Handling Pattern

Location: cli/commands/auth/logout.ts:17-25

The error handling for getAuthToken() treats all errors as "already logged out", but it could fail for various reasons (network errors, corrupted files, etc.). Only the specific "no token" case should show "Already logged out".

9. Missing Documentation

Location: CLAUDE.md

The file documents auth login but not auth logout. Should add documentation for consistency.

10. Missing JSDoc Comments

Location: common/logger-actions.ts:6

Adding JSDoc to the enum would help explain its purpose, especially given the comment about Webpack issues.


✅ What's Good

  1. Clean code structure - Well organized, follows existing patterns
  2. Good TypeScript types - Proper use of type inference and generics
  3. I18n coverage - All user-facing strings are properly internationalized
  4. Follows existing patterns - Consistent with the auth login command structure
  5. Cross-platform compatible - Uses path.join() and Node.js APIs correctly
  6. Proper use of lockfile - The pattern is correct (just needs to be moved earlier)
  7. Security conscious - Revokes token on server, not just local cleanup

📋 Recommendations

Priority 1 (Before Merge)

  1. Fix the race condition by moving lockAppdata() before revokeAuthToken()
  2. Improve error handling to distinguish between server and local failures
  3. Update the failing test on line 75 after fixing the race condition
  4. Handle 404 responses as success in revokeAuthToken()

Priority 2 (Follow-up PR)

  1. Add missing test coverage for edge cases
  2. Improve error specificity in getAuthToken() error handling
  3. Update CLAUDE.md with logout command documentation
  4. Make test assertions more robust

Priority 3 (Optional)

  1. Add JSDoc comments
  2. Remove redundant method parameter
  3. Refactor test mocks for better maintainability

🔍 Cross-Platform Review

No cross-platform issues found - The code properly uses:

  • path.join() for file paths
  • Node.js APIs that work across platforms
  • Existing getAppdataDirectory() utility that handles Windows/macOS/Linux differences

🛡️ Security Review

Security is generally good:

  • Token revocation happens server-side
  • No sensitive data logged
  • Uses existing OAuth infrastructure
  • Follows principle of least privilege

⚠️ One concern: If the server revocation succeeds but local cleanup fails, an invalid token remains in local storage. Not a security issue per se (token is invalid), but could cause confusion.


📊 Performance

No performance concerns - The command is lightweight and performs only necessary operations.


Overall Assessment

This is a solid implementation that follows the codebase patterns well. The main concerns are around error handling edge cases and a race condition that could cause issues in concurrent scenarios. The tests are comprehensive but validate some buggy behavior.

Recommendation: Address the critical issues (race condition and error state handling) before merging. The other issues can be addressed in follow-up PRs if time is constrained.

Great work overall! The code is clean, well-structured, and follows the established patterns. 🎉


@claude
Copy link

claude bot commented Nov 17, 2025

Claude Code is working…

I'll analyze this and get back to you.

View job run

await lockAppdata();
const userData = await readAppdata();
delete userData.authToken;
await saveAppdata( userData );
Copy link

Choose a reason for hiding this comment

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

Security Issue: Token remains valid after local deletion

The current implementation has a critical security flaw: if the revokeAuthToken() call succeeds but the local deletion fails (lines 30-33), the user will see an error message, but their token has already been revoked on the server. This means:

  1. The token is revoked remotely (server-side)
  2. The local deletion fails (e.g., file permission issue)
  3. User sees "Failed to log out" error
  4. The invalid token remains in local appdata

Suggested fix:

try {
	await revokeAuthToken( token.accessToken );
} catch ( error ) {
	logger.reportError( new LoggerError( __( 'Failed to revoke token on server' ), error ) );
	return; // Don't proceed with local deletion if server revocation failed
}

// Now attempt local cleanup
try {
	await lockAppdata();
	const userData = await readAppdata();
	delete userData.authToken;
	await saveAppdata( userData );
	logger.reportSuccess( __( 'Successfully logged out' ) );
} catch ( error ) {
	// Token is revoked on server but local cleanup failed
	logger.reportError( new LoggerError( __( 'Token revoked but failed to update local config. Please try again.' ), error ) );
} finally {
	await unlockAppdata();
}

This ensures the error messages accurately reflect the state and prevents confusion.

try {
await revokeAuthToken( token.accessToken );

await lockAppdata();
Copy link

Choose a reason for hiding this comment

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

Race Condition: Lock acquired AFTER API call

The lockAppdata() is called after revokeAuthToken(), which creates a race condition. Consider this scenario:

  1. User A runs studio auth logout (Process A starts)
  2. Process A calls revokeAuthToken() - succeeds
  3. User A runs studio auth login from another terminal (Process B starts)
  4. Process B locks appdata, writes new token
  5. Process A locks appdata (waiting for Process B)
  6. Process A deletes the NEW token that was just created

Fix: Move the lock acquisition before the revoke call:

try {
	await lockAppdata();
	await revokeAuthToken( token.accessToken );
	const userData = await readAppdata();
	delete userData.authToken;
	await saveAppdata( userData );
	logger.reportSuccess( __( 'Successfully logged out' ) );
} catch ( error ) {
	// error handling
} finally {
	await unlockAppdata();
}

This ensures the entire logout operation is atomic.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's unlikely, but I think it's a good approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree the case that Claude describes is very unlikely, but I still realize that lockAppdata should always be the first thing we call in a try..catch block to prevent cases where we unlock appdata without actually having acquired the lock.

await runCommand();

expect( getAuthToken ).toHaveBeenCalled();
expect( lockAppdata ).not.toHaveBeenCalled();
Copy link

Choose a reason for hiding this comment

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

Test Issue: Incorrect assertion - lock should NOT be called

This test expects lockAppdata to not be called when token revocation fails, but the current implementation (line 30 in logout.ts) calls lockAppdata() AFTER revokeAuthToken().

If revokeAuthToken() throws an error, the code jumps to the catch block at line 36, and lockAppdata() is never reached. So this assertion is actually testing the bug mentioned in the other comment about the race condition.

When the race condition is fixed (by moving lockAppdata() before revokeAuthToken()), this test will fail and needs to be updated to:

expect( lockAppdata ).toHaveBeenCalled();
expect( unlockAppdata ).toHaveBeenCalled();

Because the lock should be acquired before attempting revocation to ensure atomicity.

expect( mockLogger.reportSuccess ).toHaveBeenCalledWith( 'Already logged out' );
} );

it( 'should unlock appdata even if save fails', async () => {
Copy link

Choose a reason for hiding this comment

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

Missing Test Coverage: Lock failure scenario

There's no test for when lockAppdata() itself fails. This is an important edge case since file locking can fail due to:

  • Another process holding the lock
  • File system permissions
  • Stale lock files

Suggested additional test:

it( 'should report an error if locking appdata fails', async () => {
	( lockAppdata as jest.Mock ).mockRejectedValue( new Error( 'Lock acquisition failed' ) );

	const { runCommand } = await import( '../logout' );
	await runCommand();

	expect( getAuthToken ).toHaveBeenCalled();
	expect( lockAppdata ).toHaveBeenCalled();
	expect( revokeAuthToken ).not.toHaveBeenCalled(); // Should not proceed
	expect( unlockAppdata ).toHaveBeenCalled(); // Should still unlock
	expect( mockLogger.reportError ).toHaveBeenCalled();
} );

Comment on lines +68 to +69
it( 'should report an error if revoking the token fails', async () => {
( revokeAuthToken as jest.Mock ).mockRejectedValue( new Error( 'Failed to revoke token' ) );
Copy link

Choose a reason for hiding this comment

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

Test Gap: Testing against implementation bug

This test verifies the current buggy behavior where token revocation failure prevents local cleanup. However, there's a better approach:

Scenario 1: Server revocation fails

  • Token remains valid on server
  • Local token should remain (current behavior is correct)

Scenario 2: Server revocation succeeds, local cleanup fails

  • Token is invalid on server
  • Local token should be cleaned up in a retry

The current implementation doesn't handle Scenario 2 well. Consider adding a test:

it( 'should handle partial failure gracefully (server revoke success, local cleanup fails)', async () => {
	( revokeAuthToken as jest.Mock ).mockResolvedValue( undefined );
	( saveAppdata as jest.Mock ).mockRejectedValue( new Error( 'Disk full' ) );

	const { runCommand } = await import( '../logout' );
	await runCommand();

	expect( revokeAuthToken ).toHaveBeenCalled();
	expect( mockLogger.reportError ).toHaveBeenCalled();
	// The error message should indicate the token is revoked on server
	// but local cleanup failed
} );

Comment on lines +168 to +179
export async function revokeAuthToken( token: string ): Promise< void > {
const wpcom = wpcomFactory( token, wpcomXhrRequest );
try {
await wpcom.req.del( {
apiNamespace: 'wpcom/v2',
path: '/studio-app/token',
method: 'DELETE',
} );
} catch ( error ) {
throw new LoggerError( __( 'Failed to revoke token' ), error );
}
}
Copy link

Choose a reason for hiding this comment

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

API Issue: Error handling masks important information

The current error handling wraps all errors in a generic "Failed to revoke token" message, which makes debugging difficult. Consider these scenarios:

  1. Network timeout - User needs to retry
  2. Token already revoked (404) - This is actually success
  3. Invalid token format (400) - Token is corrupted
  4. Server error (500) - WordPress.com API issue

Suggested improvement:

export async function revokeAuthToken( token: string ): Promise< void > {
	const wpcom = wpcomFactory( token, wpcomXhrRequest );
	try {
		await wpcom.req.del( {
			apiNamespace: 'wpcom/v2',
			path: '/studio-app/token',
			method: 'DELETE',
		} );
	} catch ( error ) {
		// If token is already revoked, treat as success
		if ( error instanceof Error && 'statusCode' in error && error.statusCode === 404 ) {
			return;
		}
		
		// Preserve the original error for better debugging
		if ( error instanceof Error ) {
			throw new LoggerError( 
				__( 'Failed to revoke token on WordPress.com' ), 
				error 
			);
		}
		
		throw new LoggerError( __( 'Failed to revoke token' ), error );
	}
}

This provides better error messages and handles the idempotent case (token already revoked).

await wpcom.req.del( {
apiNamespace: 'wpcom/v2',
path: '/studio-app/token',
method: 'DELETE',
Copy link

Choose a reason for hiding this comment

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

Minor: Redundant method parameter

The wpcom.req.del() method already implies DELETE, so the method: 'DELETE' parameter is redundant. Looking at other uses of wpcom.req.del() in this file (line 125), the method parameter is not included.

Suggested fix:

await wpcom.req.del( {
	apiNamespace: 'wpcom/v2',
	path: '/studio-app/token',
} );

This keeps the code consistent with the rest of the file.

Comment on lines +17 to +25
logger.reportStart( LoggerAction.LOGOUT, __( 'Logging out…' ) );
let token: Awaited< ReturnType< typeof getAuthToken > >;

try {
token = await getAuthToken();
} catch ( error ) {
logger.reportSuccess( __( 'Already logged out' ) );
return;
}
Copy link

Choose a reason for hiding this comment

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

Code Quality: Inconsistent with login command pattern

The login command (lines 24-30 in login.ts) silently catches errors from getAuthToken() and proceeds with authentication. The logout command does the same but reports "Already logged out" as success.

However, there's an inconsistency in the user experience:

  • Login: If already authenticated, shows "Already authenticated with WordPress.com" (line 26)
  • Logout: If not authenticated, shows "Already logged out" (line 23)

But getAuthToken() can fail for multiple reasons:

  1. No token exists (truly logged out) ✅
  2. Token expired (should we revoke expired tokens?)
  3. Token invalid/corrupted (edge case)
  4. Network error during validation (line 156 in appdata.ts calls validateAccessToken)
  5. Appdata file missing/corrupted

Suggestion: Consider being more specific about the reason:

try {
	token = await getAuthToken();
} catch ( error ) {
	// Check if it's specifically "no token" vs other errors
	if ( error instanceof LoggerError && error.message.includes( 'Authentication required' ) ) {
		logger.reportSuccess( __( 'Already logged out' ) );
		return;
	}
	// For other errors (corrupted file, network issues), report them
	logger.reportError( error instanceof LoggerError ? error : new LoggerError( __( 'Failed to check authentication status' ), error ) );
	return;
}

Comment on lines 33 to 49
beforeEach( () => {
jest.clearAllMocks();

mockLogger = {
reportStart: jest.fn(),
reportSuccess: jest.fn(),
reportError: jest.fn(),
};

( Logger as jest.Mock ).mockReturnValue( mockLogger );
( getAuthToken as jest.Mock ).mockResolvedValue( mockAppdata.authToken );
( revokeAuthToken as jest.Mock ).mockResolvedValue( undefined );
( lockAppdata as jest.Mock ).mockResolvedValue( undefined );
( unlockAppdata as jest.Mock ).mockResolvedValue( undefined );
( readAppdata as jest.Mock ).mockResolvedValue( JSON.parse( JSON.stringify( mockAppdata ) ) );
( saveAppdata as jest.Mock ).mockResolvedValue( undefined );
} );
Copy link

Choose a reason for hiding this comment

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

Test Quality: Mock setup could be more maintainable

The mock setup is good, but using JSON.parse( JSON.stringify( mockAppdata ) ) on line 47 to deep clone is a bit obscure. Consider using a factory function for clarity:

const createMockAppdata = () => ( {
	authToken: {
		accessToken: 'existing-token',
		id: 999,
		email: '[email protected]',
		displayName: 'Existing User',
		expiresIn: 1209600,
		expirationTime: Date.now() + 1209600000,
	},
} );

beforeEach( () => {
	// ...
	( readAppdata as jest.Mock ).mockResolvedValue( createMockAppdata() );
} );

This makes the intent clearer and is easier to maintain. Minor nitpick though - current code works fine.

expect( revokeAuthToken ).toHaveBeenCalled();
expect( lockAppdata ).toHaveBeenCalled();
expect( readAppdata ).toHaveBeenCalled();
expect( saveAppdata ).toHaveBeenCalledWith( {} );
Copy link

Choose a reason for hiding this comment

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

Test Issue: Fragile assertion

The test asserts saveAppdata is called with an empty object {}, but this is fragile because:

  1. The readAppdata() mock returns an object with authToken property
  2. The code deletes authToken from the object
  3. The resulting object is passed to saveAppdata()

However, the mock on line 47 returns a new object each time (due to deep clone), so the assertion works. But if readAppdata were to return additional properties in the future (like version, sites, etc.), this assertion would fail incorrectly.

Better assertion:

expect( saveAppdata ).toHaveBeenCalledWith( 
	expect.not.objectContaining( { authToken: expect.anything() } )
);

Or even better, verify the token was removed:

const savedData = ( saveAppdata as jest.Mock ).mock.calls[0][0];
expect( savedData ).not.toHaveProperty( 'authToken' );

This tests the actual requirement: "authToken should be deleted from appdata".


export enum AuthCommandLoggerAction {
LOGIN = 'login',
LOGOUT = 'logout',
Copy link

Choose a reason for hiding this comment

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

Documentation: Consider adding JSDoc

Since this enum is shared between CLI and main app (as noted in the comment), it would be helpful to document what each action represents:

/**
 * Logger actions for authentication commands.
 * These actions are used for telemetry and progress reporting.
 */
export enum AuthCommandLoggerAction {
	/** User login to WordPress.com */
	LOGIN = 'login',
	/** User logout from WordPress.com */
	LOGOUT = 'logout',
}

This is especially helpful given the note about avoiding Webpack issues - future developers will understand why this file exists separately.

@@ -0,0 +1,55 @@
import { __ } from '@wordpress/i18n';
Copy link

Choose a reason for hiding this comment

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

Documentation Missing: CLAUDE.md needs update

The CLAUDE.md file documents the auth login command (lines 52-82) but doesn't include documentation for the new auth logout command.

Suggested addition after line 82 in CLAUDE.md:

#### `studio auth logout`
Log out from WordPress.com and revoke the access token.

**Usage:**
```bash
node dist/cli/main.js auth logout

Description:
This command logs you out from WordPress.com by:

  1. Revoking the access token on the WordPress.com server
  2. Removing the token from your local app data
  3. Syncing the logout state with the Studio desktop app

Options:

  • None required

Example:

npm run cli:build
node dist/cli/main.js auth logout
# Output: ✓ Successfully logged out

Notes:

  • If already logged out, the command will notify you without error
  • Logout is shared between the CLI and the Studio desktop app
  • The token is revoked on WordPress.com, invalidating all sessions using that token

This maintains consistency with the existing documentation style and helps users understand the command.

Base automatically changed from f26d/cli-auth-login-command to trunk November 17, 2025 14:37
@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Documentation Update Complete

I've successfully reviewed the CLI changes and updated the documentation for the new auth logout command.

Tasks

  • Review the new auth logout command implementation
  • Check documentation structure (docs/ai-instructions.md vs CLAUDE.md)
  • Update CLAUDE.md with auth logout command documentation
  • Update docs/ai-instructions.md with auth logout command documentation
  • Commit changes

What Was Done

Command Implementation Review

The new studio auth logout command:

  • Revokes the access token on WordPress.com server via REST API
  • Clears the token from local app data
  • Properly handles edge cases (already logged out, errors)
  • Uses file locking for safe concurrent access
  • Shares authentication state with the Studio desktop app

Documentation Updates

Added comprehensive documentation for the auth logout command to both CLAUDE.md and docs/ai-instructions.md, including:

  • Command syntax: node dist/cli/main.js auth logout
  • Step-by-step description of what the command does (revoke token, clear local data, sync with desktop app)
  • Example usage with expected output
  • Important notes about behavior and token revocation

The documentation follows the same format as the existing auth login command documentation, ensuring consistency.

Changes Committed

Created commit e21171ae with the documentation updates. The changes are ready to be pushed, but there appear to be newer commits on the remote branch that would need to be fetched first (requires approval for git fetch/git pull).


Note on Push

The documentation changes have been committed locally. To push them to the remote branch, you'll need to run:

git pull --rebase origin f26d/cli-auth-logout-command
git push origin f26d/cli-auth-logout-command

This is needed because there are remote changes that aren't in the local copy yet (the push was rejected with "fetch first" message).


@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Documentation Updated

I've reviewed the new studio auth logout command implementation and updated the CLI documentation in docs/ai-instructions.md.

Tasks Completed

  • Review the new auth logout command implementation
  • Check documentation structure (docs/ai-instructions.md vs CLAUDE.md)
  • Update documentation with new command details
  • Commit and push changes

Changes Made

Added documentation for the studio auth logout command in docs/ai-instructions.md (lines 84-111), following the same format as the existing auth login command. The documentation includes:

  • Command syntax and usage
  • Description of what the command does (revokes token on server, clears local data, syncs with desktop app)
  • Example usage
  • Important notes about behavior

Notes

  • CLAUDE.md is a symlink to docs/ai-instructions.md, so updating one file updated both
  • The new command implementation is solid and follows established patterns
  • The command properly handles token revocation, file locking, and error cases
  • Comprehensive test coverage is already included in the PR

Commit: ffd836f - "docs: Update CLI documentation"

github-actions bot and others added 2 commits November 17, 2025 14:41
Add documentation for the new `studio auth logout` command, following the same format as the existing `auth login` command documentation.

Co-authored-by: Fredrik Rombach Ekelund <[email protected]>
@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Documentation Review Complete

I've reviewed the CLI changes and documentation for the new studio auth logout command.

Tasks

  • Review the new auth logout command implementation
  • Check current documentation in docs/ai-instructions.md
  • Verify documentation is complete and accurate
  • Update documentation if needed
  • Commit changes if updates were made

Summary

The documentation for the studio auth logout command is already complete and up-to-date. No changes are needed.

What I Found

Command Implementation (cli/commands/auth/logout.ts):

  • Properly implements logout functionality
  • Revokes token on WordPress.com server via REST API
  • Clears token from local app data
  • Uses file locking for safe concurrent access
  • Handles edge cases (already logged out, errors)

Documentation Status (docs/ai-instructions.md, lines 84-111):

  • ✅ Complete documentation for studio auth logout command already exists
  • ✅ Follows the same format as the auth login command documentation
  • ✅ Includes usage, description, examples, and notes
  • ✅ Properly describes the three-step process (revoke, clear, sync)
  • ✅ Documents important behaviors (already logged out, token invalidation)

File Structure:

  • CLAUDE.md is a symlink to docs/ai-instructions.md, so updating one updates both
  • The documentation was added in a previous commit (based on the changed_files list showing +29 lines)

Conclusion

No documentation updates are required. The CLI documentation comprehensively covers the new auth logout command with clear usage instructions, examples, and notes about its behavior.


Copy link
Contributor

@bcotrim bcotrim left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Tested and worked as described on macOS and Windows.
Added some non-blocking considerations

}

try {
await revokeAuthToken( token.accessToken );
Copy link
Contributor

Choose a reason for hiding this comment

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

In Studio, failing to revoke the auth token doesn't fail the logout operation.
Have you considered keeping the same behavior in the CLI?

Copy link
Contributor

Choose a reason for hiding this comment

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

If we consider the revoke of the token as non-critical we should do the api call after removing the token from appdata, preventing the unlikely scenario of having a revoked token store in appdata

try {
await revokeAuthToken( token.accessToken );

await lockAppdata();
Copy link
Contributor

Choose a reason for hiding this comment

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

It's unlikely, but I think it's a good approach.

@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


CLI Implementation Review Complete

I've reviewed the new studio auth logout command implementation and documentation.

Tasks

  • Review the new auth logout command implementation
  • Check current documentation status in docs/ai-instructions.md
  • Verify documentation completeness and accuracy
  • Assess implementation quality and adherence to patterns

Summary

The documentation is already complete and accurate. The studio auth logout command has been properly documented in docs/ai-instructions.md (lines 84-111), following the same format as the auth login command. No documentation updates are needed.

Documentation Status ✅

The documentation includes:

  • Command syntax: node dist/cli/main.js auth logout
  • Description: 3-step process (revoke on server, clear local data, sync with desktop)
  • Usage example: With expected output
  • Important notes: Already logged out behavior, token invalidation, desktop app sync

The documentation is comprehensive and follows the established format in the repository.

Implementation Review

What's Good ✅

  1. Clean architecture - Follows existing patterns from auth login command
  2. Comprehensive test coverage - 4 test cases covering success, failure, and edge cases
  3. Proper error handling - Uses try/catch with LoggerError for consistent error reporting
  4. File locking - Uses lockAppdata()/unlockAppdata() to prevent race conditions
  5. I18n support - All user-facing strings use __() for internationalization
  6. Cross-platform compatibility - Uses standard Node.js APIs
  7. Desktop app sync - Changes to appdata automatically sync with Studio desktop app

Implementation Details

Files Changed:

  • cli/commands/auth/logout.ts - New logout command (54 lines)
  • cli/commands/auth/tests/logout.test.ts - Comprehensive tests (113 lines)
  • cli/index.ts - Command registration
  • cli/lib/api.ts - New revokeAuthToken() function
  • common/logger-actions.ts - Added LOGOUT action for telemetry
  • docs/ai-instructions.md - Documentation (already complete)

Command Flow:

  1. Check for existing auth token via getAuthToken()
  2. If no token exists, report "Already logged out" and exit
  3. Lock appdata file to prevent concurrent modifications
  4. Call WordPress.com API to revoke the token server-side
  5. Remove token from local appdata file
  6. Unlock appdata file
  7. Report success or error

Observations from Review Comments

I noticed reviewer @bcotrim raised an interesting consideration about the revocation behavior:

"In Studio, failing to revoke the auth token doesn't fail the logout operation. Have you considered keeping the same behavior in the CLI?"

The current CLI implementation treats server revocation failure as a hard error (line 28-32 in logout.ts). This differs from the desktop app's behavior. The current approach is defensible because:

  • Security-first: Ensures the token is actually revoked server-side
  • CLI context: CLI users expect commands to fail loudly when operations don't complete
  • Consistency: Matches the pattern where auth operations are atomic

However, if you prefer to match the desktop app's behavior (local-first logout), that would be a valid design choice too.

Code Quality Notes

The implementation has one minor observation:

  • Line 174 in cli/lib/api.ts: The method: 'DELETE' parameter is redundant when using wpcom.req.del() (the method is implied by the .del() function)

This is cosmetic and doesn't affect functionality.


Conclusion

Documentation is complete - No updates needed
Implementation follows patterns - Consistent with existing code
Tests are comprehensive - Good coverage of edge cases
Ready for merge - No blocking issues found

The PR successfully implements the studio auth logout command with proper documentation, tests, and error handling. Great work!

@claude
Copy link

claude bot commented Nov 17, 2025

Claude finished @fredrikekelund's task —— View job


Documentation Status: Up to Date ✅

I've reviewed the recent changes to this PR and verified the documentation state.

Tasks

  • Check the latest commits to understand what changed
  • Review current documentation state
  • Verify if documentation updates are needed
  • Provide final assessment

Analysis

Recent Commits:

  1. b6d4c684 - Fix test
  2. c11f125d - Lock appfile before calling API (fixed race condition)
  3. d660ff04 - Tweaks
  4. ffd836fe - docs: Update CLI documentation

Documentation Status:
The CLI documentation in docs/ai-instructions.md (lines 84-111) is already complete and comprehensive for the studio auth logout command. It includes:

  • ✅ Command syntax: node dist/cli/main.js auth logout
  • ✅ Step-by-step description of the logout process
  • ✅ Usage examples with expected output
  • ✅ Important notes about behavior and token revocation

Implementation Review:
The most recent implementation change (commit c11f125d) addressed a critical race condition identified in the code review by moving lockAppdata() before revokeAuthToken(). This is an internal implementation detail that:

  • Prevents concurrent login/logout operations from corrupting token state
  • Ensures the entire logout operation is atomic
  • Does not change the user-facing behavior or command usage

Conclusion

No documentation updates are needed. The recent changes are internal implementation improvements that don't affect the documented behavior or usage of the studio auth logout command. The documentation accurately describes what the command does from a user perspective.

The PR has comprehensive documentation, proper test coverage, and has received multiple approvals from reviewers. The implementation correctly handles:

  • Token revocation on WordPress.com server
  • Local token cleanup with proper file locking
  • Error handling for edge cases
  • Sync with Studio desktop app

@fredrikekelund fredrikekelund merged commit 696e923 into trunk Nov 17, 2025
9 of 10 checks passed
@fredrikekelund fredrikekelund deleted the f26d/cli-auth-logout-command branch November 17, 2025 16:02
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.

5 participants